效率 | 如何优雅地自动生成一篇菲林日记?
我与胶片的故事
从今年年初开始,源于对摄影的喜爱,我就开始了进一步的探索,也主要源于我特别喜欢的一个up主(links),看了很多他的视频就慢慢接触到了“胶片”这个在上世纪十分流行的影像载体。并且在很短的时间里疯狂了解各个相机品牌的种类和机型,也逐渐购入了一些我很喜欢的胶片相机,有135和120,区别是拍摄的底片画幅不同。135的有尼康的f2,120的有哈苏的500c,有了相机不够啊,还得多配几个镜头,不同焦段的,还得准备多几个后背,不同胶卷换着拍。OK这下好了,开始入坑 🤸🏻♂️
于我而言,使用胶片和胶片相机进行摄影创作,是一种很特别又很有趣的体验。
1️⃣ 这些胶片相机大多做工都很优秀,机身很有质感,把玩性很高。现在能买到的胶片相机都基本是在上世纪生产的,因为到了本世纪初胶片的潮流已经被数码所取代了。所以一般情况下你能够以一个很经济的价格买到一台在以前价格贵到飞起的旗舰机型,就比如尼康的f系列、哈苏的500系列、徕卡的m系列、Mamiya的rb、rz系列,还有我最近很喜欢的勃朗尼卡etrs系列。这些相机真的跟现在市面上这些外观、功能无限趋同的数码相机很不一样,以前那些相机每一台都有着很精致的设计和做工用料,其中不乏很多精密的机械结构,带来的直接体验就是,按快门的触感和声音特别让人陶醉,然后拍摄时操作相机进行调整参数、上卷、过片、取景、对焦这些,都十分有趣。
如果你想进军中画幅,数码的就不用想了,最经济的选择其实就是这些120胶片相机。135一般可以拍摄36张,120一般可以拍摄12张。胶片相机是摄影领域的经典设备,代表着不同年代胶片相机的顶尖技术水平。每个时代的胶片相机都承载着当时各家相机制造商的最高技术成果和创新。它们记录着历史时刻、捕捉了珍贵的瞬间,并通过胶片的质感和色彩展现出独特的影像风格。
2️⃣ 跟胶片照片最终成像有影响的是两样东西,一是相机镜头,二是胶卷本身。胶片时代生产的镜头也跟这些机身一样,做工很扎实,满满的机械感。而胶卷也有柯达、富士、伊尔福等等这些厂家在生产,也涉及到不同型号不同感光度,比如著名的有柯达的gold 200、ektar 100 160、portra 400。不同胶卷的成像都有各自的特性,或颗粒细腻或色彩还原饱满,不同感光度也有各自适合拍摄的场景。这些都给胶片摄影带来了很多可能性和可玩性。
3️⃣ 而在拍摄体验上面,有区别于数码相机的拍摄体验,拍摄胶片的基本流程是,首先你需要先购买胶片,因为胶片商家停产和各方面的原因,而今胶片已经相比三四年前的价格涨了五倍左右,这真的是一个噩耗。所以在现在这个时间点选择拍胶片,真的不是一个经济的选择。这是我最近拍摄胶片的花费:
然后就是实际的拍摄,涉及到规划一次旅行做攻略这些,也是因为胶片价格贵呀,所以要省着在出去玩的时候去拍,在遇到有意思的画面的时候去拍,然后因为我手里的胶片相机带的都是手动对焦的镜头,并且没有测光功能,所以通常的一套标准拍摄过程大概就是,用手机的app对想拍的画面进行测光,得到光圈、快门这些参数之后,来到相机上进行设置,再然后从取景器里对画面进行手动对焦,最后按下一声清脆的快门。
拍摄好的胶片会找到淘宝店家进行冲洗、扫描,这个过程大概耗时五天左右,最后店家才会给到你最后的数码照片。
至今为止,我已经拍摄了有七卷胶片,有黑白有彩色的,每一卷都去到了很好玩的地方,我也为每一卷编写了单独的文章,并贴上了所有拍摄的样张,我应该会一直以这种形式做下去,让这种特别的载体能够时刻让我能够很方便地回忆起过去的旅行和生活。https://chenxuefan.cn/tags/images/film/
那么问题来了,每一篇菲林日记都需要手动上传图片、编写固定的内容,而且以后还会重复很多次这个动作。于是本着「能自动化绝不手动」的初衷,就有了下面的这个项目。
https://www.sebastian-schlueter.com/blog/2018/3/7/the-magic-fuji-frontier-sp-3000
生成菲林日记
胶片也被称为菲林,源于其最早的商标名称"Kodak Film"(柯达菲林)。“菲林"一词是柯达公司创始人乔治·伊斯曼(George Eastman)为其胶片产品取的名称,用来代表柯达公司所生产的可替换胶片。现在在一些玩家这里也经常称其作菲林,而非胶片。
需求梳理
首先分析一下生成一篇菲林日记的工作流sop:
- 整理目录下所有的照片,为其用从小到大的数字进行重命名,得到 1.jpg、2.jpg…
- 压缩所有的照片,至2000k以下,以达到节约流量、照片在页面加载速度加快的目的
- 上传所有的照片,使用API上传至阿里云oss云存储仓库,即可通过线上访问到照片资源
- 编写菲林日记,将固定的内容用md格式写入一份md文件
- 手动编写最终的信息,如文章标题、胶卷型号、相机型号、拍摄地点
代码编写
有时候收到冲洗扫描好的照片之后,可能当下我没有精力去处理,或者对成片不太满意就没那个动力去修图,所以可能这一卷要隔一段时间再来处理。在这期间可能有新的卷被处理,比如第75卷的照片我没有来得及处理,但是后来的第80卷我比第75卷先一步处理完了,那么这个创建时间就要根据75卷的前后两卷的时间进行折中,得到75这一卷的创建时间。
如果不属于上面的情况,也就是当前处理的这一卷是最新的,那么就取图片目录的创建时间(这个时间是修图后导出文件的时间),再减去3天的时间(因为要算上冲洗、修图),作为这篇日志的创建时间。
主要也是为尽可能把这篇日记的时间更接近于拍摄的那一天。
下面是获取每一篇日记创建时间的脚本:
def get_create_timestamp(film_diary_dir: str, film_diary_num: str) -> float:
"""获取文件的创建时间"""
import subprocess
# 取图片文件的创建时间
# 如果film目录下有比当前目录名更大的,则获取更大目录的时间和更小目录的时间,取两者的中间时间,作为当前目录的创建时间
# 如果没有,则返回当前目录的创建时间
film_dir = os.path.split(film_diary_dir)[0]
if film_diary_num == '1' or \
(os.path.exists(os.path.join(film_dir, str(int(film_diary_num) - 1))) and
os.path.exists(os.path.join(film_dir, str(int(film_diary_num) + 1)))):
res = subprocess.run(['stat', '-f%B', film_diary_dir], capture_output=True, text=True)
c_timestamp = int(res.stdout.strip())
return c_timestamp
bigger_film_diary_c_timestamp = 0
max_film_diary_num = max([int(each) for each in os.listdir(film_dir) if each.isdigit()])
for num in range(int(film_diary_num) + 1, max_film_diary_num): # 顺序获取
bigger_film_diary_dir = os.path.join(film_dir, str(num))
if os.path.exists(bigger_film_diary_dir):
res = subprocess.run(['stat', '-f%B', bigger_film_diary_dir], capture_output=True, text=True)
bigger_film_diary_c_timestamp = int(res.stdout.strip())
break
else:
res = subprocess.run(['stat', '-f%B', film_diary_dir], capture_output=True, text=True)
c_timestamp = int(res.stdout.strip())
return c_timestamp
smaller_film_diary_c_timestamp = 0
for num in range(int(film_diary_num) - 1, 1, -1): # 倒序获取
smaller_film_diary_dir = os.path.join(film_dir, str(num))
if os.path.exists(smaller_film_diary_dir):
res = subprocess.run(['stat', '-f%B', smaller_film_diary_dir], capture_output=True, text=True)
smaller_film_diary_c_timestamp = int(res.stdout.strip())
break
c_timestamp = (smaller_film_diary_c_timestamp + bigger_film_diary_c_timestamp) / 2
return c_timestamp
贴出一段生成文章内容的函数:
def write_a_md(upload_files: list, md_file_name: str, y: int, m: int, d: int):
md_str = '---\n' \
'title: 菲林日记 | | | \n' \
'author: Billy Chen\n' \
'date: {}+08:00\n' \
'tags: [film,]\n' \
'keywords: [film,]\n' \
'categories: [life]\n' \
'type: blog\n' \
'---\n' \
'\n' \
'🎞️ - \n\n' \
'📷 - + \n\n' \
'🖨 - \n\n' \
'🏞 - \n\n' \
'\n\n' \
'{}'
# ![]()**
# add all photos' link to the text
img_str = ''''''
upload_files.sort(key=lambda i: int(os.path.splitext(i)[0].split('/')[-1]))
for file in upload_files:
num = os.path.splitext(file)[0].split('/')[-1]
file_name = os.path.split(file)[1]
file_path = file.split('compress/')[1]
img_str += f'![{file_name}]' \
f'(https://{config.bucket_name_img}.{config.endpoint}/{file_path})' \
f'*{num}*' \
f'\n\n'
# old
# md_file_path = os.path.join(config.posts_path, f'{y}/{m}', md_file_name)
# new
str_m = f'0{m}' if len(str(m)) == 1 else m
str_d = f'0{d}' if len(str(d)) == 1 else d
md_str = md_str.format(f'{y}-{str_m}-{str_d}T{time.strftime("%H:%M:%S")}', img_str)
md_file_path = os.path.join(config.film_path, md_file_name)
with open(md_file_path, 'w') as f:
f.write(md_str)
logger.info(f'菲林日记生成成功 - {md_file_name}')
后记
这张是我用尼康f2拍摄的第一卷胶卷中的一张,去了香港的维多利亚港。我特别喜欢这个一个人看海的画面,很能够让人带入画面里的情境。
于我而言,我很享受用胶片进行创作的过程,我也应该会一直坚持下去。