Open richardmyu opened 3 years ago
控制台执行 hexo new page "album"
,然后会在 source
文件看到 album
以及 index.md
文件。
然后在主题下的配置文件(themes/next/_config.yml
)增加 tab:
menu:
...
# 新增
album: /album/ || fa fa-camera
图标可以自己更换,如果有语言切换,记得去主题的 languages
文件增加相册的对应语言词汇。
单纯只要一级相册是挺简单的。但这里的一级相册,是要为二级相册做准备的。
这里,我们使用模板来生成 album 页面,具体是在 themes/next/layout
下新建 album.swig
文件:
{% extends '_layout.swig' %}
{% import '_macro/sidebar.swig' as sidebar_template with context %}
{# 添加相册 title #}
{% block title %}{{ page.title }} | {{ title }}{% endblock %}
{% block content %}
<div class="posts-expand">
<div class="post-block" lang="{{ page.lang or page.language or config.language }}">
{# 不想在相册页面显示 “相册”,则注释下一条代码 #}
{# {% include '_partials/page/page-header.swig' %} #}
<div class="post-body{%- if page.direction and page.direction.toLowerCase() === 'rtl' %} rtl{%- endif %}">
{% if config.album %}
<div class="album-wrapper row">
{% for gallery in config.album.gallery %}
<div class="album-box">
<a href="./{{ gallery.name }}" class="album-item">
<div class="album-cover-box" style="background-image: url({{ config.album.image_bed }}/{{ gallery.name }}/thumbnail/{{ gallery.cover }});">
</div>
<p class="album-name">
{{ gallery.name }}
</p>
</a>
<p class="album-description">
{{ gallery.description }}
<p>
</div>
{% endfor %}
</div>
{% endif %}
</div>
</div>
</div>
{% endblock %}
{% block sidebar %}
{{ sidebar_template.render(true) }}
{% endblock %}
细节上,可以根据自己的实际需求进行修改调整。然后要在站点配置文件(_config.yml
)中,添加相册的信息:
album:
imageBed: https://xxx.xx.com/album
gallery:
- name: 'xxx'
cover: 'xxx.jpg'
description: 'xx...xx'
created: 'xxxx-xx-xx'
对于相册而已,这里只是生成了 html 页面,如有需要你可以根据 css 来定制对应的样式。这里 css 数据放在了souce/_data/styles.styl
中。
最后修改 source/album/index.md
文件:
---
date: xxxx-xx-xx xx:xx:xx
layout: album
title: '相册'
---
layout: album
用来指定使用 album.swig
来渲染出相册的 HTML 页面。
在 source/album
下,创建 gallery 目录,具体是指,根据站点配置信息,建立对应的图册目录。
album:
imageBed: https://xxx.xx.com/album
gallery:
- name: 'flowers'
cover: 'xxx.jpg'
description: 'xx...xx'
created: 'xxxx-xx-xx'
- name: 'sky'
cover: 'xxx.jpg'
description: 'xx...xx'
created: 'xxxx-xx-xx'
对应 gallery :
|-- album
| |
| |-- index.md
| |
| |-- flowers
| | |
| | |-- data.json
| | |
| | |-- index.md
| | |
| |
| |-- sky
| | |
| | |-- data.json
| | |
| | |-- index.md
| | |
| |
| |-- ...
|
下面是描述相册的 JSON 文件,可以通过 Python 脚本自动生成和更新(后面有讲述)。
{
"name": "flowers",
"cover": "xx.jpg",
"description": "xx...xx",
"created": "xxxx-xx-xx",
"imageBed": "https://xxx.xxcom/album",
"items": [
{
"date": "2019-11",
"images": [
{
"name": "xxx.jpg",
"caption": "xx....xxx",
"type": "image",
"date": "xxxx-xx-xx",
"address": "xx,xxx,xxx",
"width": 6400,
"height": 4000
}
]
}
]
}
下面是 gallery 的 index.md
文件:
---
date: xxxx-xx-xx xx:xx:xx
fancybox: false
layout: gallery
title: 'flowers'
galleryName: 'flowers'
---
接下来,就要编写 gallery 的模板了。跟一级相册一样,还是在在 themes/next/layout
下新建 gallery.swig
文件:
{% extends '_layout.swig' %}
{% import '_macro/sidebar.swig' as sidebar_template with context %}
{% block title %}{{ page.title }} | {{ title }}{% endblock %}
{% block content %}
<div class="posts-expand">
<div class="post-block" lang="{{ page.lang or page.language or config.language }}">
{# {% include '_partials/page/page-header.swig' %} #}
<div class="post-body{%- if page.direction and page.direction.toLowerCase() === 'rtl' %} rtl{%- endif %}">
<link rel="stylesheet" href="/lib/album/photoswipe.css">
<link rel="stylesheet" href="/lib/album/default-skin/default-skin.css">
{# gallery body #}
<div class="gallery">
<div class="gallery-description">
<p id="gallery-description">这里是相册描述</p>
</div>
<div class="instagram itemscope">
<a href="https://richyu.gitee.io/" target="_blank" class="open-ins">图片正在加载中…</a>
</div>
</div>
{# gallery body end #}
{# <!-- Core JS file --> #}
<script src="/lib/album/photoswipe.min.js"></script>
{# <!-- UI JS file --> #}
<script src="/lib/album/photoswipe-ui-default.min.js"></script>
{# <!-- gallery.js --> #}
<script>
(function() {
var loadScript = function(path) {
var $script = document.createElement('script')
document.getElementsByTagName('body')[0].appendChild($script)
$script.setAttribute('src', path)
}
setTimeout(function() {
loadScript('/lib/album/gallery.min.js')
}, 0)
})()
</script>
</div>
{# {% include '_partials/page/breadcrumb.swig' %} #}
</div>
</div>
{# <!-- Root element of PhotoSwipe. Must have class pswp. --> #}
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">
{# <!-- Background of PhotoSwipe.
It's a separate element, as animating opacity is faster than rgba(). --> #}
<div class="pswp__bg"></div>
{# <!-- Slides wrapper with overflow:hidden. --> #}
<div class="pswp__scroll-wrap">
{# <!-- Container that holds slides. PhotoSwipe keeps only 3 slides in DOM to save memory. --> #}
{# <!-- don't modify these 3 pswp__item elements, data is added later on. --> #}
<div class="pswp__container">
<div class="pswp__item"></div>
<div class="pswp__item"></div>
<div class="pswp__item"></div>
</div>
{# <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. --> #}
<div class="pswp__ui pswp__ui--hidden">
<div class="pswp__top-bar">
{# <!-- Controls are self-explanatory. Order can be changed. --> #}
<div class="pswp__counter"></div>
<button class="pswp__button pswp__button--close" title="Close (Esc)"></button>
{# <button class="pswp__button pswp__button--share" title="Share"></button> #}
<button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>
<button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>
{# <!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR --> #}
{# <!-- element will get class pswp__preloader--active when preloader is running --> #}
<div class="pswp__preloader">
<div class="pswp__preloader__icn">
<div class="pswp__preloader__cut">
<div class="pswp__preloader__donut"></div>
</div>
</div>
</div>
</div>
<div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
<div class="pswp__share-tooltip"></div>
</div>
<button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)"></button>
<button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)"></button>
<div class="pswp__caption">
<div class="pswp__caption__center"></div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block sidebar %}
{{ sidebar_template.render(true) }}
{% endblock %}
到此为止,页面基本完成,剩下的就是编写 gallery.js
了。
在 themes/next/source/lib
下,新建 album 目录,创建一下文件:
|
|-- album
| |
| |-- assets
| | |
| | |-- empty,png
| | |
| |
| |-- default-skin
| | |
| | |-- default-skin.css
| | |
| | |-- default-skin.png
| | |
| | |-- default-skin.svg
| | |
| | |-- preloader.gif
| | |
| |
| |-- gallery.js
| |
| |-- handler_photoswipe.js
| |
| |-- photoswipe-ui-default.min.js
| |
| |-- photoswipe.css
| |
| |-- photoswipe.min.js
| |
|
以上这些图片以及脚本均可以在 https://github.com/richardmyu/richardmyu.github.io/tree/main/lib/album 中找到。
其中一个重点在 gallery.js
文件中。
这是参考文章中的脚本:
"use strict";
require('lazyloadjs');
var photoswipe = require("./handler_photoswipe.js");
var view = _interopRequireDefault(photoswipe.viewer);
var galleryPath = window.location.pathname;
var galleryName = galleryPath.split("/")[2];
var dataUrl = '/album/' + galleryName + '/data.json';
var dataJSON;
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
var render = function render(response) {
var imageBed = response["image_bed"];
var description = response["description"];
var items = response["items"];
var ulTmpl = "";
for (var item of items) {
var liTmpl = "";
var date = item['date']
var year = date.split('-')[0];
var month = date.split('-')[1];
for (var img of item["images"]) {
var thumbnail = imageBed + '/' + galleryName + "/thumbnail/" + img.name;
var artwork = imageBed + '/' + galleryName + "/artwork/" + img.name;
var caption = img.caption;
var address = img.address
var width = img.width;
var height = img.height;
liTmpl += `
<figure class="thumb" itemprop="associatedMedia" itemscope="" itemtype="http://schema.org/ImageObject">
<a href="${artwork}" itemprop="contentUrl" data-size="${width}x${height}">
<img class="reward-img" data-src="${thumbnail}" src="/lib/album/assets/empty.png" itemprop="thumbnail" onload="lzld(this)">
</a>
<figcaption style="display:none;" itemprop="caption description">${address || caption}</figcaption>
</figure>`;
}
ulTmpl += `
<section class="archives album">
<h3 class="timeline">${year} 年 ${month} 月</h3>
<div class="img-box">${liTmpl}</div>
</section>`;
}
document.querySelector('.instagram').innerHTML = `<div class=${photoswipe.galleryClass} itemscope="" itemtype="http://schema.org/ImageGallery">${ulTmpl}</div>`;
document.querySelector('#gallery-description').innerHTML = description;
view.default.init();
};
function loadData(render) {
if (!dataJSON) {
var xhr = new XMLHttpRequest();
xhr.open('GET', dataUrl, true);
xhr.onload = function () {
if (this.status >= 200 && this.status < 300) {
dataJSON = JSON.parse(this.response);
render(dataJSON);
} else {
console.error(this.statusText);
}
};
xhr.onerror = function () {
console.error(this.statusText);
};
xhr.send();
} else {
render(dataJSON);
}
}
var Gallery = {
init: function init() {
loadData(function (data) {
render(data);
});
}
};
Gallery.init();
需要注意的一点是:
ulTmpl += `
<section class="archives album">
<h3 class="timeline">${year} 年 ${month} 月</h3>
<div class="img-box">${liTmpl}</div>
</section>`;
从 <section class="archives album">
到 ${liTmpl}
,中间只能嵌套一层;若想要嵌套多层,要修改 handler_photoswipe.js
:
var parseThumbnailElements = function parseThumbnailElements(el) {
// 两次获取父节点,限定了最多只能嵌套一层,
// 超过一层(多层情况也一致),会因为祖父节点定位在单个照片组之内,
// 使得 photoswipe 将照片组甚至单个照片视为一个相册,
// 从而造成点击的照片在“相册”中的索引与当前与全局获取图片的索引不一致
// 结果就是点击图片,放大的是另一张图片
el = el.parentNode.parentNode;
var thumbElements = el.getElementsByClassName('thumb')
// ...
// 获取该页面全部图片 `thumb`
childNodes = document.getElementsByClassName('thumb')
放大图片的规则:当前点击照片全局索引 a,当前“相册” 照片数 x,放大的图片在该“相册”内的索引是 a;自然会有 a > x 的情况,那结果就是取“相册”的第一张照片。
上面说到相册的数据是从 data.json
读取的,如果照片或者相册比较多,那么写 JSON 会很无聊的,所以我们使用 Python 来完成这个工作。
因为我使用了 Gitee 的仓库来作为图床,所以便也把 python 脚本也写在了图床仓库里。
这一部分参考 Hexo NexT 添加多级相册功能 就可以了,实际也可以根据自己的需求,进行部分修改和调整,比如我就增加了批量添加图片,解析 GPS 获取地址以及拍摄日期(个人觉得从图片名称获取日期不确定性比较大,甚至可能得修改图片名称)。
为了获取 Exif 信息,肯定得放上原图,但原图如果上传到图库,也不合适(:smirk:那你还不是主动提供了地址。。。:joy::joy::joy:),所以我多增加了一个文件,用来存放原图,且不上传的。
@staticmethod
def reset_orientation(img):
"""
处理图片的自动(PIL处理回发生)旋转
增加对 width / height 的调换处理
https://cloud.tencent.com/developer/article/1523050
"""
exif_orientation_tag = 274
w, h = img.size
if hasattr(img, "_getexif") and isinstance(img._getexif(), dict) and exif_orientation_tag in img._getexif():
exif_data = img._getexif()
orientation = exif_data[exif_orientation_tag]
# Handle EXIF Orientation
if orientation == 1:
# Normal image - nothing to do!
pass
elif orientation == 2:
# Mirrored left to right
# transpose 翻转
img = img.transpose(Image.FLIP_LEFT_RIGHT)
elif orientation == 3:
# Rotated 180 degrees
img = img.rotate(180)
elif orientation == 4:
# Mirrored top to bottom
img = img.rotate(180).transpose(Image.FLIP_LEFT_RIGHT)
elif orientation == 5:
# Mirrored along top-left diagonal
img = img.rotate(-90, expand=True).transpose(Image.FLIP_LEFT_RIGHT)
w, h = h, w
elif orientation == 6:
# Rotated 90 degrees
img = img.rotate(-90, expand=True)
w, h = h, w
elif orientation == 7:
# Mirrored along top-right diagonal
img = img.rotate(90, expand=True).transpose(Image.FLIP_LEFT_RIGHT)
w, h = h, w
elif orientation == 8:
# Rotated 270 degrees
img = img.rotate(90, expand=True)
w, h = h, w
return {
"image": img,
"size": (w, h)
}
@staticmethod
def reverse_geocoder(geolocator, lat_lon, sleep_sec=5):
"""
根据经纬度,计算区域
https://www.pythonheidong.com/blog/article/680556/d9bf76d691415de282f1/
"""
try:
# 反向地理编码
return geolocator.reverse(lat_lon)
except GeocoderTimedOut:
print('Timeout: GeocoderTimedOut Retrying...')
time.sleep(randint(2 * 100, sleep_sec * 100) / 50)
return AlbumTool.reverse_geocoder(geolocator, lat_lon, sleep_sec)
except GeocoderServiceError as e:
print('CONNECTION REFUSED: GeocoderServiceError encountered.')
print(e)
return None
except Exception as e:
print('ERROR: Terminating due to exception {}'.format(e))
return None
@staticmethod
def get_exif(img):
"""
获取图片的经纬度以及拍摄时间等信息
https://zhuanlan.zhihu.com/p/98460548
"""
print("Loading and Resolving: " + img)
f = open(img, 'rb')
image_map = exifread.process_file(f)
try:
# 图片的经度
img_longitude_ref = image_map["GPS GPSLongitudeRef"].printable
img_longitude = image_map["GPS GPSLongitude"].printable[1:-1].replace(" ", "").replace("/", ",").split(
",")
img_longitude = float(img_longitude[0]) + float(img_longitude[1]) / 60 + float(
img_longitude[2]) / float(img_longitude[3]) / 3600
if img_longitude_ref != "E":
img_longitude = img_longitude * (-1)
# 图片的纬度
img_latitude_ref = image_map["GPS GPSLatitudeRef"].printable
img_latitude = image_map["GPS GPSLatitude"].printable[1:-1].replace(" ", "").replace("/", ",").split(
",")
img_latitude = float(img_latitude[0]) + float(img_latitude[1]) / 60 + float(img_latitude[2]) / float(
img_latitude[3]) / 3600
if img_latitude_ref != "N":
img_latitude = img_latitude * (-1)
except Exception as e:
print('ERROR: 图片中不包含 Gps 信息')
img_latitude = ''
img_longitude = ''
# 照片拍摄时间
img_create_date = image_map["Image DateTime"].printable[:10].replace(":", "-")
f.close()
location = None
if img_latitude != '' and img_longitude != '':
reverse_value = str(img_latitude) + ', ' + str(img_longitude)
# 初始化 Nominatim() 时传入新的 user-agent 值,避开样例值
user_agent = 'my_blog_agent_{}'.format(randint(10000, 99999))
geolocator = Nominatim(user_agent=user_agent)
location = AlbumTool.reverse_geocoder(geolocator, reverse_value)
address = location and location.address or ''
info = {
'address': address, # 地址
'date': img_create_date # 日期
}
return info
批量添加很简单就不说了。目前为止,相册部分算是基本完工了。效果见 album。
with open(self.data_json) as json_file:
data = json.load(json_file)
items = data["items"]
这行代码可能会二次读取 JSON 时,造成乱码,解决方法就是指定读取格式:
with open(self.data_json, 'r', encoding='utf-8') as json_file
前言
前一段时间,整理手机的时候,发现手机内的照片太多了,而那个时候也在写一些书摘,就想着,要不在博客上建立一个相册吧?在搭建和配置这个博客(Hexo & Next)的时候,已经知道不支持相册功能,但实在是很想弄一个,于是上网搜搜看,看也没有可以借鉴的。
在看来了多个文章之后,选择了其中几篇比较详尽,功能比较符合需求的文章为借鉴,根据自己的实际情况,以及自己的需求,做出一些修改和调整,并在此记录下来。
1.Hexo NexT 添加多级相册功能 2.搭建Hexo博客相册 3.Next -23- 添加相册系列 -3- 获取图像信息、保存为json文件并上传图像