mirror of
https://github.com/rr-/szurubooru.git
synced 2025-07-17 08:26:24 +00:00
Compare commits
20 Commits
bfe3767b68
...
2fb2ffb2e1
Author | SHA1 | Date | |
---|---|---|---|
2fb2ffb2e1 | |||
3f42037e2b | |||
7972c34448 | |||
a496e8980f | |||
096b6bc61e | |||
41a681b254 | |||
c843bbb35e | |||
74eaa22662 | |||
ad622c4d99 | |||
3d27bcaab5 | |||
2d71ea0e05 | |||
ac303db9e6 | |||
7405593101 | |||
5877ad9463 | |||
f956d8033c | |||
571cd90fd2 | |||
436a693be1 | |||
4220ae708d | |||
64c5eec3d2 | |||
80840b9509 |
@ -106,6 +106,11 @@ form .fa-question-circle-o
|
||||
background-color: $scrollbar-bg-color
|
||||
&::-webkit-scrollbar-thumb
|
||||
background-color: $scrollbar-thumb-color
|
||||
li[data-name=view]
|
||||
background: $button-enabled-background-color
|
||||
margin-right: 1em
|
||||
a
|
||||
color: $button-enabled-text-color
|
||||
>.content-wrapper:not(.transparent)
|
||||
background: $top-navigation-color
|
||||
padding: 1.8em
|
||||
@ -214,8 +219,6 @@ nav
|
||||
ul li[data-name=settings],
|
||||
ul li[data-name=help]
|
||||
float: none
|
||||
.access-key
|
||||
text-decoration: underline
|
||||
.thumbnail
|
||||
width: 1.5em
|
||||
height: 1.5em
|
||||
@ -244,9 +247,6 @@ nav
|
||||
#mobile-navigation-toggle
|
||||
color: $text-color-darktheme
|
||||
|
||||
a .access-key
|
||||
text-decoration: underline
|
||||
|
||||
.messages
|
||||
margin: 0 auto
|
||||
text-align: left
|
||||
@ -284,28 +284,25 @@ a .access-key
|
||||
/*background-image: attr(data-src url)*/ /* not available yet */
|
||||
vertical-align: middle
|
||||
background-repeat: no-repeat
|
||||
background-size: cover
|
||||
background-size: contain
|
||||
background-position: center
|
||||
display: inline-block
|
||||
overflow: hidden
|
||||
width: 20px
|
||||
height: 20px
|
||||
&.empty
|
||||
background-image:
|
||||
linear-gradient(45deg, $transparency-grid-square-color 25%, transparent 25%),
|
||||
linear-gradient(-45deg, $transparency-grid-square-color 25%, transparent 25%),
|
||||
linear-gradient(45deg, transparent 75%, $transparency-grid-square-color 75%),
|
||||
linear-gradient(-45deg, transparent 75%, $transparency-grid-square-color 75%)
|
||||
background-position: 0 0, 0 10px, 10px -10px, -10px 0px
|
||||
background-repeat: repeat
|
||||
background-size: 20px 20px
|
||||
img
|
||||
repeating-linear-gradient(45deg, $window-color, $window-color 10px, #e6e6e6 10px, #e6e6e6 20px)
|
||||
img, video
|
||||
opacity: 0
|
||||
width: auto
|
||||
height: 100%
|
||||
video
|
||||
width: auto
|
||||
object-fit: cover
|
||||
width: 100%
|
||||
height: 100%
|
||||
|
||||
.darktheme .thumbnail.empty
|
||||
background-image:
|
||||
repeating-linear-gradient(45deg, $window-color-darktheme, $window-color-darktheme 10px, #333 10px, #333 20px)
|
||||
|
||||
.flexbox-dummy
|
||||
height: 0 !important
|
||||
padding-top: 0 !important
|
||||
|
@ -1,14 +1,16 @@
|
||||
@import colors
|
||||
|
||||
.post-container
|
||||
.post-content.transparency-grid img
|
||||
background-image:
|
||||
linear-gradient(45deg, $transparency-grid-square-color 25%, transparent 25%),
|
||||
linear-gradient(-45deg, $transparency-grid-square-color 25%, transparent 25%),
|
||||
linear-gradient(45deg, transparent 75%, $transparency-grid-square-color 75%),
|
||||
linear-gradient(-45deg, transparent 75%, $transparency-grid-square-color 75%)
|
||||
background-size: 20px 20px
|
||||
background-position: 0 0, 0 10px, 10px -10px, -10px 0px
|
||||
.post-content
|
||||
&.transparency-grid, &.post-error
|
||||
background-image:
|
||||
linear-gradient(45deg, $transparency-grid-square-color 25%, transparent 25%),
|
||||
linear-gradient(-45deg, $transparency-grid-square-color 25%, transparent 25%),
|
||||
linear-gradient(45deg, transparent 75%, $transparency-grid-square-color 75%),
|
||||
linear-gradient(-45deg, transparent 75%, $transparency-grid-square-color 75%)
|
||||
background-position: 0 0, 0 10px, 10px -10px, -10px 0px
|
||||
background-repeat: repeat
|
||||
background-size: 20px 20px
|
||||
|
||||
text-align: center
|
||||
.post-content
|
||||
@ -17,6 +19,8 @@
|
||||
position: relative
|
||||
|
||||
.resize-listener
|
||||
background-repeat: no-repeat
|
||||
background-size: cover
|
||||
position: absolute
|
||||
left: 0
|
||||
right: 0
|
||||
@ -27,3 +31,14 @@
|
||||
|
||||
img
|
||||
image-orientation: from-image
|
||||
|
||||
.darktheme .post-container .post-content
|
||||
&.transparency-grid, &.post-error
|
||||
background-image:
|
||||
linear-gradient(45deg, $transparency-grid-square-color 25%, transparent 25%),
|
||||
linear-gradient(-45deg, $transparency-grid-square-color 25%, transparent 25%),
|
||||
linear-gradient(45deg, transparent 75%, $transparency-grid-square-color 75%),
|
||||
linear-gradient(-45deg, transparent 75%, $transparency-grid-square-color 75%)
|
||||
background-position: 0 0, 0 10px, 10px -10px, -10px 0px
|
||||
background-repeat: repeat
|
||||
background-size: 20px 20px
|
||||
|
@ -62,22 +62,22 @@ $cancel-button-color = tomato
|
||||
margin: 0 0 1.2em 0
|
||||
padding-left: 13em
|
||||
|
||||
img
|
||||
width: 100%
|
||||
height: 100%
|
||||
|
||||
video
|
||||
width: 100%
|
||||
height: 100%
|
||||
|
||||
&>.thumbnail-wrapper
|
||||
float: left
|
||||
width: 12em
|
||||
height: 8em
|
||||
margin: 0 0 0 -13em
|
||||
a
|
||||
display: block
|
||||
height: 100%
|
||||
width: 100%
|
||||
.thumbnail
|
||||
width: 100%
|
||||
height: 100%
|
||||
video
|
||||
opacity: 1
|
||||
img, video
|
||||
object-fit: contain
|
||||
|
||||
.uploadable
|
||||
border: 1px solid $upload-border-color
|
||||
|
@ -1,13 +1,14 @@
|
||||
<div class='post-content post-type-<%- ctx.post.type %>'>
|
||||
<% if (['image', 'animation'].includes(ctx.post.type)) { %>
|
||||
|
||||
<img class='resize-listener' alt='' src='<%- ctx.post.contentUrl %>'/>
|
||||
<img class='resize-listener' alt='' src='<%- ctx.post.contentUrl %>' draggable='false' fetchPriority='high'/>
|
||||
|
||||
<% } else if (ctx.post.type === 'flash') { %>
|
||||
|
||||
<object class='resize-listener' width='<%- ctx.post.canvasWidth %>' height='<%- ctx.post.canvasHeight %>' data='<%- ctx.post.contentUrl %>'>
|
||||
<param name='wmode' value='opaque'/>
|
||||
<param name='wmode' value='transparent'/>
|
||||
<param name='movie' value='<%- ctx.post.contentUrl %>'/>
|
||||
<div class='messages'><div class='message-wrapper'><div class='message error'>Your browser does not support Flash.</div></div></div>
|
||||
</object>
|
||||
|
||||
<% } else if (ctx.post.type === 'video') { %>
|
||||
@ -19,6 +20,8 @@
|
||||
loop: (ctx.post.flags || []).includes('loop'),
|
||||
playsinline: true,
|
||||
autoplay: ctx.autoplay,
|
||||
preload: 'auto',
|
||||
poster: ctx.post.originalThumbnailUrl,
|
||||
},
|
||||
ctx.makeElement('source', {
|
||||
type: ctx.post.mimeType,
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
<div class='thumbnail'>
|
||||
<a href='<%= ctx.uploadable.previewUrl %>'>
|
||||
<video id='video' nocontrols muted>
|
||||
<video nocontrols muted>
|
||||
<source type='<%- ctx.uploadable.mimeType %>' src='<%- ctx.uploadable.previewUrl %>'/>
|
||||
</video>
|
||||
</a>
|
||||
|
@ -294,11 +294,16 @@ class Api extends events.EventTarget {
|
||||
// transform the request: upload each file, then make the request use
|
||||
// its tokens.
|
||||
data = Object.assign({}, data);
|
||||
let fileData = {};
|
||||
let abortFunction = () => {};
|
||||
let promise = Promise.resolve();
|
||||
if (files) {
|
||||
for (let key of Object.keys(files)) {
|
||||
const file = files[key];
|
||||
if (file === null) {
|
||||
fileData[key] = null;
|
||||
continue;
|
||||
}
|
||||
const fileId = this._getFileId(file);
|
||||
if (fileTokens[fileId]) {
|
||||
data[key + "Token"] = fileTokens[fileId];
|
||||
@ -324,7 +329,7 @@ class Api extends events.EventTarget {
|
||||
url,
|
||||
requestFactory,
|
||||
data,
|
||||
{},
|
||||
fileData,
|
||||
options
|
||||
);
|
||||
abortFunction = () => requestPromise.abort();
|
||||
@ -388,7 +393,7 @@ class Api extends events.EventTarget {
|
||||
if (files) {
|
||||
for (let key of Object.keys(files)) {
|
||||
const value = files[key];
|
||||
if (value.constructor === String) {
|
||||
if (value !== null && value.constructor === String) {
|
||||
data[key + "Url"] = value;
|
||||
} else {
|
||||
req.attach(key, value || new Blob());
|
||||
|
@ -8,7 +8,7 @@ const PageController = require("../controllers/page_controller.js");
|
||||
const CommentsPageView = require("../views/comments_page_view.js");
|
||||
const EmptyView = require("../views/empty_view.js");
|
||||
|
||||
const fields = ["id", "comments", "commentCount", "thumbnailUrl"];
|
||||
const fields = ["id", "comments", "commentCount", "thumbnailUrl", "customThumbnailUrl"];
|
||||
|
||||
class CommentsController {
|
||||
constructor(ctx) {
|
||||
|
@ -14,6 +14,7 @@ const EmptyView = require("../views/empty_view.js");
|
||||
const fields = [
|
||||
"id",
|
||||
"thumbnailUrl",
|
||||
"customThumbnailUrl",
|
||||
"type",
|
||||
"safety",
|
||||
"score",
|
||||
|
@ -178,15 +178,11 @@ class PostMainController extends BasePostController {
|
||||
if (e.detail.relations !== undefined) {
|
||||
post.relations = e.detail.relations;
|
||||
}
|
||||
if (e.detail.content !== undefined) {
|
||||
post.newContent = e.detail.content;
|
||||
}
|
||||
if (e.detail.thumbnail !== undefined) {
|
||||
post.newThumbnail = e.detail.thumbnail;
|
||||
}
|
||||
if (e.detail.source !== undefined) {
|
||||
post.source = e.detail.source;
|
||||
}
|
||||
post.newContent = e.detail.content;
|
||||
post.newThumbnail = e.detail.thumbnail;
|
||||
post.save().then(
|
||||
() => {
|
||||
this._view.sidebarControl.showSuccess("Post saved.");
|
||||
|
@ -119,9 +119,28 @@ class PostContentControl {
|
||||
post: this._post,
|
||||
autoplay: settings.get().autoplayVideos,
|
||||
});
|
||||
if (settings.get().transparencyGrid) {
|
||||
function load(argument) {
|
||||
if (settings.get().transparencyGrid) {
|
||||
newNode.classList.add("transparency-grid");
|
||||
}
|
||||
newNode.firstElementChild.style.backgroundImage = "";
|
||||
}
|
||||
if (["image", "flash"].includes(this._post.type)) {
|
||||
newNode.firstElementChild.style.backgroundImage = "url("+this._post.originalThumbnailUrl+")";
|
||||
}
|
||||
if (this._post.type == "image") {
|
||||
newNode.firstElementChild.addEventListener("load", load);
|
||||
} else if (settings.get().transparencyGrid) {
|
||||
newNode.classList.add("transparency-grid");
|
||||
}
|
||||
newNode.firstElementChild.addEventListener("error", (e) => {
|
||||
newNode.classList.add("post-error");
|
||||
if (["image", "animation"].includes(this._post.type)) {
|
||||
newNode.firstElementChild.removeEventListener("load", load);
|
||||
newNode.firstElementChild.style.backgroundImage = "url("+this._post.originalThumbnailUrl+")";
|
||||
newNode.firstElementChild.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
|
||||
}
|
||||
});
|
||||
if (this._postContentNode) {
|
||||
this._hostNode.replaceChild(newNode, this._postContentNode);
|
||||
} else {
|
||||
|
@ -138,10 +138,7 @@ class PostEditSidebarControl extends events.EventTarget {
|
||||
this._thumbnailRemovalLinkNode.addEventListener("click", (e) =>
|
||||
this._evtRemoveThumbnailClick(e)
|
||||
);
|
||||
this._thumbnailRemovalLinkNode.style.display = this._post
|
||||
.hasCustomThumbnail
|
||||
? "block"
|
||||
: "none";
|
||||
this._thumbnailRemovalLinkUpdate(this._post);
|
||||
}
|
||||
|
||||
if (this._addNoteLinkNode) {
|
||||
@ -249,12 +246,25 @@ class PostEditSidebarControl extends events.EventTarget {
|
||||
this._poolsExpander.title = `Pools (${this._post.pools.length})`;
|
||||
}
|
||||
|
||||
_thumbnailRemovalLinkUpdate(post) {
|
||||
if (this._thumbnailRemovalLinkNode) {
|
||||
this._thumbnailRemovalLinkNode.style.display = post
|
||||
.customThumbnailUrl
|
||||
? "block"
|
||||
: "none";
|
||||
}
|
||||
}
|
||||
|
||||
_evtPostContentChange(e) {
|
||||
this._contentFileDropper.reset();
|
||||
this._thumbnailRemovalLinkUpdate(e.detail.post);
|
||||
this._newPostContent = null;
|
||||
}
|
||||
|
||||
_evtPostThumbnailChange(e) {
|
||||
this._thumbnailFileDropper.reset();
|
||||
this._thumbnailRemovalLinkUpdate(e.detail.post);
|
||||
this._newPostThumbnail = undefined;
|
||||
}
|
||||
|
||||
_evtRemoveThumbnailClick(e) {
|
||||
@ -427,9 +437,7 @@ class PostEditSidebarControl extends events.EventTarget {
|
||||
: undefined,
|
||||
|
||||
thumbnail:
|
||||
this._newPostThumbnail !== undefined
|
||||
? this._newPostThumbnail
|
||||
: undefined,
|
||||
this._newPostThumbnail,
|
||||
|
||||
source: this._sourceInputNode
|
||||
? this._sourceInputNode.value
|
||||
|
@ -70,6 +70,14 @@ class Post extends events.EventTarget {
|
||||
return this._thumbnailUrl;
|
||||
}
|
||||
|
||||
get customThumbnailUrl() {
|
||||
return this._customThumbnailUrl;
|
||||
}
|
||||
|
||||
get originalThumbnailUrl() {
|
||||
return this._originalThumbnailUrl;
|
||||
}
|
||||
|
||||
get source() {
|
||||
return this._source;
|
||||
}
|
||||
@ -146,10 +154,6 @@ class Post extends events.EventTarget {
|
||||
return this._ownScore;
|
||||
}
|
||||
|
||||
get hasCustomThumbnail() {
|
||||
return this._hasCustomThumbnail;
|
||||
}
|
||||
|
||||
set flags(value) {
|
||||
this._flags = value;
|
||||
}
|
||||
@ -477,7 +481,9 @@ class Post extends events.EventTarget {
|
||||
response.contentUrl,
|
||||
document.getElementsByTagName("base")[0].href
|
||||
).href,
|
||||
_thumbnailUrl: response.thumbnailUrl,
|
||||
_thumbnailUrl: response.customThumbnailUrl ? response.customThumbnailUrl : response.thumbnailUrl,
|
||||
_customThumbnailUrl: response.customThumbnailUrl,
|
||||
_originalThumbnailUrl: response.thumbnailUrl,
|
||||
_source: response.source,
|
||||
_canvasWidth: response.canvasWidth,
|
||||
_canvasHeight: response.canvasHeight,
|
||||
@ -491,7 +497,6 @@ class Post extends events.EventTarget {
|
||||
_favoriteCount: response.favoriteCount,
|
||||
_ownScore: response.ownScore,
|
||||
_ownFavorite: response.ownFavorite,
|
||||
_hasCustomThumbnail: response.hasCustomThumbnail,
|
||||
});
|
||||
|
||||
for (let obj of [this, this._orig]) {
|
||||
|
@ -49,7 +49,7 @@ function makeThumbnail(url) {
|
||||
style: `background-image: url(\'${url}\')`,
|
||||
}
|
||||
: { class: "thumbnail empty" },
|
||||
makeElement("img", { alt: "thumbnail", src: url })
|
||||
makeElement("img", { alt: "thumbnail", src: url, draggable: "false" })
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -401,6 +401,14 @@ class PostUploadView extends events.EventTarget {
|
||||
.addEventListener("click", (e) =>
|
||||
this._evtMoveClick(e, uploadable, 1)
|
||||
);
|
||||
if (uploadable.type == "video") {
|
||||
const video = rowNode.querySelector("video");
|
||||
if (video) {
|
||||
video.addEventListener("loadedmetadata", (e) => {
|
||||
if (!isNaN(video.duration)) video.currentTime = Math.floor(video.duration * 0.3)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_updateThumbnailNode(uploadable) {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import os
|
||||
import glob
|
||||
from typing import Any, List, Optional
|
||||
|
||||
from szurubooru import config
|
||||
@ -24,6 +25,10 @@ def scan(path: str) -> List[Any]:
|
||||
return []
|
||||
|
||||
|
||||
def find(path: str, pattern: str, recursive: bool = False) -> List[Any]:
|
||||
return glob.glob(glob.escape(_get_full_path(path) + "/") + pattern, recursive=recursive)
|
||||
|
||||
|
||||
def move(source_path: str, target_path: str) -> None:
|
||||
os.rename(_get_full_path(source_path), _get_full_path(target_path))
|
||||
|
||||
|
@ -24,10 +24,18 @@ def convert_heif_to_png(content: bytes) -> bytes:
|
||||
return img_byte_arr.getvalue()
|
||||
|
||||
|
||||
def check_for_loop(content: bytes) -> bytes:
|
||||
img = PILImage.open(BytesIO(content))
|
||||
return "loop" in img.info
|
||||
|
||||
|
||||
class Image:
|
||||
def __init__(self, content: bytes) -> None:
|
||||
self.content = content
|
||||
self._reload_info()
|
||||
if self.info["format"]["format_name"] == "swf":
|
||||
self.content = self.swf_to_png()
|
||||
self._reload_info()
|
||||
|
||||
@property
|
||||
def width(self) -> int:
|
||||
@ -41,7 +49,7 @@ class Image:
|
||||
def frames(self) -> int:
|
||||
return self.info["streams"][0]["nb_read_frames"]
|
||||
|
||||
def resize_fill(self, width: int, height: int) -> None:
|
||||
def resize_fill(self, width: int, height: int, keep_transparency: bool = True, seek=True) -> None:
|
||||
width_greater = self.width > self.height
|
||||
width, height = (-1, height) if width_greater else (width, -1)
|
||||
|
||||
@ -50,8 +58,12 @@ class Image:
|
||||
"{path}",
|
||||
"-f",
|
||||
"image2",
|
||||
"-filter:v",
|
||||
"scale='{width}:{height}'".format(width=width, height=height),
|
||||
"-filter_complex",
|
||||
(
|
||||
"format=rgb32,scale={width}:{height}:flags=bicubic"
|
||||
if keep_transparency else
|
||||
"[0:v]format=rgb32,scale={width}:{height}:flags=bicubic[a];color=white[b];[b][a]scale2ref[b][a];[b][a]overlay"
|
||||
).format(width=width, height=height),
|
||||
"-map",
|
||||
"0:v:0",
|
||||
"-vframes",
|
||||
@ -60,10 +72,7 @@ class Image:
|
||||
"png",
|
||||
"-",
|
||||
]
|
||||
if (
|
||||
"duration" in self.info["format"]
|
||||
and self.info["format"]["format_name"] != "swf"
|
||||
):
|
||||
if seek and "duration" in self.info["format"]:
|
||||
duration = float(self.info["format"]["duration"])
|
||||
if duration > 3:
|
||||
cli = [
|
||||
@ -76,6 +85,19 @@ class Image:
|
||||
self.content = content
|
||||
self._reload_info()
|
||||
|
||||
def swf_to_png(self) -> bytes:
|
||||
return self._execute(
|
||||
[
|
||||
"--silent",
|
||||
"-g",
|
||||
"gl",
|
||||
"--",
|
||||
"{path}",
|
||||
"-",
|
||||
],
|
||||
program="exporter",
|
||||
)
|
||||
|
||||
def to_png(self) -> bytes:
|
||||
return self._execute(
|
||||
[
|
||||
@ -96,24 +118,13 @@ class Image:
|
||||
def to_jpeg(self) -> bytes:
|
||||
return self._execute(
|
||||
[
|
||||
"-f",
|
||||
"lavfi",
|
||||
"-i",
|
||||
"color=white:s=%dx%d" % (self.width, self.height),
|
||||
"-i",
|
||||
"-quality",
|
||||
"85",
|
||||
"-sample",
|
||||
"1x1",
|
||||
"{path}",
|
||||
"-f",
|
||||
"image2",
|
||||
"-filter_complex",
|
||||
"overlay",
|
||||
"-map",
|
||||
"0:v:0",
|
||||
"-vframes",
|
||||
"1",
|
||||
"-vcodec",
|
||||
"mjpeg",
|
||||
"-",
|
||||
]
|
||||
],
|
||||
program="cjpeg",
|
||||
)
|
||||
|
||||
def to_webm(self) -> bytes:
|
||||
@ -274,7 +285,10 @@ class Image:
|
||||
with util.create_temp_file(suffix="." + extension) as handle:
|
||||
handle.write(self.content)
|
||||
handle.flush()
|
||||
cli = [program, "-loglevel", "32" if get_logs else "24"] + cli
|
||||
if program in ["ffmpeg", "ffprobe"]:
|
||||
cli = [program, "-loglevel", "32" if get_logs else "24"] + cli
|
||||
else:
|
||||
cli = [program] + cli
|
||||
cli = [part.format(path=handle.name) for part in cli]
|
||||
proc = subprocess.Popen(
|
||||
cli,
|
||||
@ -285,7 +299,7 @@ class Image:
|
||||
out, err = proc.communicate()
|
||||
if proc.returncode != 0:
|
||||
logger.warning(
|
||||
"Failed to execute ffmpeg command (cli=%r, err=%r)",
|
||||
"Failed to execute {program} command (cli=%r, err=%r)".format(program=program),
|
||||
" ".join(shlex.quote(arg) for arg in cli),
|
||||
err,
|
||||
)
|
||||
@ -315,7 +329,7 @@ class Image:
|
||||
)
|
||||
assert "format" in self.info
|
||||
assert "streams" in self.info
|
||||
if len(self.info["streams"]) < 1:
|
||||
if len(self.info["streams"]) < 1 and self.info["format"]["format_name"] != "swf":
|
||||
logger.warning("The video contains no video streams.")
|
||||
raise errors.ProcessingError(
|
||||
"The video contains no video streams."
|
||||
|
@ -124,6 +124,15 @@ def get_post_thumbnail_url(post: model.Post) -> str:
|
||||
)
|
||||
|
||||
|
||||
def get_post_custom_thumbnail_url(post: model.Post) -> str:
|
||||
assert post
|
||||
return "%s/generated-thumbnails/custom-thumbnails/sample_%d_%s.jpg" % (
|
||||
config.config["data_url"].rstrip("/"),
|
||||
post.post_id,
|
||||
get_post_security_hash(post.post_id),
|
||||
)
|
||||
|
||||
|
||||
def get_post_content_path(post: model.Post) -> str:
|
||||
assert post
|
||||
assert post.post_id
|
||||
@ -134,6 +143,15 @@ def get_post_content_path(post: model.Post) -> str:
|
||||
)
|
||||
|
||||
|
||||
def get_post_custom_content_path(post: model.Post) -> str:
|
||||
assert post
|
||||
assert post.post_id
|
||||
return "posts/custom-thumbnails/%d_%s.dat" % (
|
||||
post.post_id,
|
||||
get_post_security_hash(post.post_id),
|
||||
)
|
||||
|
||||
|
||||
def get_post_thumbnail_path(post: model.Post) -> str:
|
||||
assert post
|
||||
return "generated-thumbnails/%d_%s.jpg" % (
|
||||
@ -142,9 +160,9 @@ def get_post_thumbnail_path(post: model.Post) -> str:
|
||||
)
|
||||
|
||||
|
||||
def get_post_thumbnail_backup_path(post: model.Post) -> str:
|
||||
def get_post_custom_thumbnail_path(post: model.Post) -> str:
|
||||
assert post
|
||||
return "posts/custom-thumbnails/%d_%s.dat" % (
|
||||
return "generated-thumbnails/custom-thumbnails/sample_%d_%s.jpg" % (
|
||||
post.post_id,
|
||||
get_post_security_hash(post.post_id),
|
||||
)
|
||||
@ -180,6 +198,7 @@ class PostSerializer(serialization.BaseSerializer):
|
||||
"canvasHeight": self.serialize_canvas_height,
|
||||
"contentUrl": self.serialize_content_url,
|
||||
"thumbnailUrl": self.serialize_thumbnail_url,
|
||||
"customThumbnailUrl": self.serialize_custom_thumbnail_url,
|
||||
"flags": self.serialize_flags,
|
||||
"tags": self.serialize_tags,
|
||||
"relations": self.serialize_relations,
|
||||
@ -195,7 +214,6 @@ class PostSerializer(serialization.BaseSerializer):
|
||||
"featureCount": self.serialize_feature_count,
|
||||
"lastFeatureTime": self.serialize_last_feature_time,
|
||||
"favoritedBy": self.serialize_favorited_by,
|
||||
"hasCustomThumbnail": self.serialize_has_custom_thumbnail,
|
||||
"notes": self.serialize_notes,
|
||||
"comments": self.serialize_comments,
|
||||
"pools": self.serialize_pools,
|
||||
@ -319,8 +337,9 @@ class PostSerializer(serialization.BaseSerializer):
|
||||
for rel in self.post.favorited_by
|
||||
]
|
||||
|
||||
def serialize_has_custom_thumbnail(self) -> Any:
|
||||
return files.has(get_post_thumbnail_backup_path(self.post))
|
||||
def serialize_custom_thumbnail_url(self) -> Any:
|
||||
if files.has(get_post_custom_thumbnail_path(self.post)):
|
||||
return get_post_custom_thumbnail_url(self.post)
|
||||
|
||||
def serialize_notes(self) -> Any:
|
||||
return sorted(
|
||||
@ -357,7 +376,7 @@ def serialize_micro_post(
|
||||
post: model.Post, auth_user: model.User
|
||||
) -> Optional[rest.Response]:
|
||||
return serialize_post(
|
||||
post, auth_user=auth_user, options=["id", "thumbnailUrl"]
|
||||
post, auth_user=auth_user, options=["id", "thumbnailUrl", "customThumbnailUrl"]
|
||||
)
|
||||
|
||||
|
||||
@ -462,32 +481,28 @@ def _before_post_delete(
|
||||
) -> None:
|
||||
if post.post_id:
|
||||
if config.config["delete_source_files"]:
|
||||
files.delete(get_post_content_path(post))
|
||||
files.delete(get_post_thumbnail_path(post))
|
||||
pattern = f"{post.post_id}_*"
|
||||
for file in files.find("posts", "**/" + pattern, recursive=True) + files.find("generated-thumbnails", "**/sample_" + pattern, recursive=True):
|
||||
files.delete(file)
|
||||
|
||||
|
||||
def _sync_post_content(post: model.Post) -> None:
|
||||
regenerate_thumb = False
|
||||
|
||||
if hasattr(post, "__content"):
|
||||
content = getattr(post, "__content")
|
||||
files.save(get_post_content_path(post), content)
|
||||
generate_post_thumbnail(get_post_thumbnail_path(post), content, seek=False)
|
||||
if mime.is_video(post.mime_type):
|
||||
generate_post_thumbnail(get_post_custom_thumbnail_path(post), content, seek=True)
|
||||
delattr(post, "__content")
|
||||
regenerate_thumb = True
|
||||
|
||||
if hasattr(post, "__thumbnail"):
|
||||
if getattr(post, "__thumbnail"):
|
||||
files.save(
|
||||
get_post_thumbnail_backup_path(post),
|
||||
getattr(post, "__thumbnail"),
|
||||
)
|
||||
thumbnail = getattr(post, "__thumbnail")
|
||||
files.save(get_post_custom_content_path(post), thumbnail)
|
||||
generate_post_thumbnail(get_post_custom_thumbnail_path(post), thumbnail, seek=True)
|
||||
else:
|
||||
files.delete(get_post_thumbnail_backup_path(post))
|
||||
files.delete(get_post_custom_thumbnail_path(post))
|
||||
delattr(post, "__thumbnail")
|
||||
regenerate_thumb = True
|
||||
|
||||
if regenerate_thumb:
|
||||
generate_post_thumbnail(post)
|
||||
|
||||
|
||||
def generate_alternate_formats(
|
||||
@ -677,22 +692,19 @@ def update_post_thumbnail(
|
||||
setattr(post, "__thumbnail", content)
|
||||
|
||||
|
||||
def generate_post_thumbnail(post: model.Post) -> None:
|
||||
assert post
|
||||
if files.has(get_post_thumbnail_backup_path(post)):
|
||||
content = files.get(get_post_thumbnail_backup_path(post))
|
||||
else:
|
||||
content = files.get(get_post_content_path(post))
|
||||
def generate_post_thumbnail(path: str, content: bytes, seek=True) -> None:
|
||||
try:
|
||||
assert content
|
||||
image = images.Image(content)
|
||||
image.resize_fill(
|
||||
int(config.config["thumbnails"]["post_width"]),
|
||||
int(config.config["thumbnails"]["post_height"]),
|
||||
keep_transparency=False,
|
||||
seek=seek,
|
||||
)
|
||||
files.save(get_post_thumbnail_path(post), image.to_jpeg())
|
||||
files.save(path, image.to_jpeg())
|
||||
except errors.ProcessingError:
|
||||
files.save(get_post_thumbnail_path(post), EMPTY_PIXEL)
|
||||
files.save(path, EMPTY_PIXEL)
|
||||
|
||||
|
||||
def update_post_tags(
|
||||
|
@ -51,7 +51,7 @@ class Context:
|
||||
use_video_downloader: bool = False,
|
||||
allow_tokens: bool = True,
|
||||
) -> bytes:
|
||||
if name in self._files and self._files[name]:
|
||||
if name in self._files:
|
||||
return self._files[name]
|
||||
|
||||
if name + "Url" in self._params:
|
||||
|
@ -72,12 +72,12 @@ def test_get_post_thumbnail_path(input_mime_type):
|
||||
|
||||
|
||||
@pytest.mark.parametrize("input_mime_type", ["image/jpeg", "image/gif"])
|
||||
def test_get_post_thumbnail_backup_path(input_mime_type):
|
||||
def test_get_post_custom_thumbnail_path(input_mime_type):
|
||||
post = model.Post()
|
||||
post.post_id = 1
|
||||
post.mime_type = input_mime_type
|
||||
assert (
|
||||
posts.get_post_thumbnail_backup_path(post)
|
||||
posts.get_post_custom_thumbnail_path(post)
|
||||
== "posts/custom-thumbnails/1_244c8840887984c4.dat"
|
||||
)
|
||||
|
||||
|
Reference in New Issue
Block a user