Files
file-manager/tools/make-demo-library.py
T

204 lines
7.0 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Builds the synthetic file library the store screenshots are taken over.
python3 tools/make-demo-library.py [output-dir] # default: ./demo-files
Why this exists: published screenshots are public. Shooting them over a real device's
storage would put someone's real file names, folder names and document titles on the Play
listing. Everything here is generated, so every pixel of every screenshot is fake by
construction.
Output lands in ./demo-files/ *inside* the repo and is git-ignored: it is regenerated on
demand and never committed. Push it to a device with:
adb push demo-files /sdcard/Download/PlainFiles-Demo
"""
import os
import shutil
import sys
import zipfile
from PIL import Image, ImageDraw, ImageFont
FONT_BOLD = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf"
# Distinct hues so the grid view reads as eight different pictures rather than a wall of one
# colour. (name, background hue, accent hue, motif)
SCENES = [
("dawn-ridge", (28, 78, 150), (255, 214, 140), "peaks"),
("pine-lake", (18, 96, 92), (198, 240, 200), "waves"),
("desert-road", (196, 120, 48), (255, 236, 196), "peaks"),
("neon-city", (78, 40, 130), (240, 120, 220), "bars"),
("quiet-harbour", (36, 62, 110), (170, 220, 255), "waves"),
("autumn-field", (150, 84, 36), (255, 208, 120), "bars"),
("snow-pass", (110, 124, 148), (245, 250, 255), "peaks"),
("orchard", (52, 108, 60), (214, 246, 170), "bars"),
]
def font(path, size):
try:
return ImageFont.truetype(path, size)
except OSError:
return ImageFont.load_default()
def vertical_gradient(width, height, top, bottom):
image = Image.new("RGB", (width, height), top)
draw = ImageDraw.Draw(image)
for y in range(height):
blend = y / max(height - 1, 1)
draw.line(
[(0, y), (width, y)],
fill=tuple(int(top[i] + (bottom[i] - top[i]) * blend) for i in range(3)),
)
return image
def draw_motif(draw, width, height, kind, colour):
if kind == "peaks":
base = height * 0.78
for offset, scale in ((0.05, 0.42), (0.42, 0.58), (0.74, 0.34)):
apex = base - height * scale
draw.polygon(
[
(width * offset, base),
(width * (offset + 0.20), apex),
(width * (offset + 0.40), base),
],
fill=colour,
)
elif kind == "waves":
for index in range(5):
y = height * (0.55 + index * 0.08)
amplitude = height * 0.03
points = [
(x, y + amplitude * ((x // 40) % 2 * 2 - 1)) for x in range(0, width + 40, 40)
]
draw.line(points, fill=colour, width=6)
else: # bars
for index in range(7):
x = width * (0.10 + index * 0.12)
bar = height * (0.20 + (index % 3) * 0.14)
draw.rounded_rectangle(
[x, height * 0.80 - bar, x + width * 0.06, height * 0.80],
radius=8,
fill=colour,
)
def make_photos(root):
folder = os.path.join(root, "Photos")
os.makedirs(folder, exist_ok=True)
label_font = font(FONT_BOLD, 54)
for index, (name, top, accent, motif) in enumerate(SCENES, start=1):
width, height = 1600, 1200
image = vertical_gradient(width, height, top, tuple(int(c * 0.45) for c in top))
draw = ImageDraw.Draw(image)
draw.ellipse([width * 0.72, height * 0.12, width * 0.92, height * 0.32], fill=accent)
draw_motif(draw, width, height, motif, accent)
draw.text((60, height - 120), f"Sample {index:02d} - {name}", font=label_font, fill=accent)
image.save(os.path.join(folder, f"sample-{index:02d}-{name}.png"), "PNG")
def write(path, text):
with open(path, "w", encoding="utf-8") as handle:
handle.write(text)
def make_text_files(root):
folder = os.path.join(root, "Documents")
os.makedirs(folder, exist_ok=True)
write(
os.path.join(folder, "offsite-notes.txt"),
"Team offsite - notes\n"
"====================\n\n"
"Nothing in this file is real: the whole library the screenshots are taken over\n"
"is generated by tools/make-demo-library.py, so publishing them leaks nobody's\n"
"data.\n\n"
"1. Agenda\n2. Travel\n3. Follow-ups\n",
)
write(
os.path.join(folder, "budget.csv"),
"item,quantity,unit_cost\n"
"laptop stand,2,39.00\n"
"usb-c hub,3,54.50\n"
"headset,1,229.00\n"
"markers,12,2.15\n",
)
write(
os.path.join(folder, "itinerary.md"),
"# Itinerary\n\n| Day | Item |\n| --- | --- |\n"
"| 1 | Arrival |\n| 2 | Workshops |\n| 3 | Departure |\n",
)
# Stand-ins for binary formats: what matters for a screenshot is the icon and the name
# in the list, and a real PDF or DOCX would add a build dependency for no visible gain.
write(os.path.join(folder, "quarterly-report.pdf"), "%PDF-1.4\n% demo placeholder\n")
write(os.path.join(folder, "service-agreement.docx"), "PK demo placeholder\n")
def make_sized_file(path, megabytes):
"""A file of a given size, so the usage screen has something worth ranking."""
chunk = b"\x00" * (1024 * 1024)
with open(path, "wb") as handle:
for _ in range(megabytes):
handle.write(chunk)
def make_media(root):
video = os.path.join(root, "Video")
os.makedirs(video, exist_ok=True)
for index in range(1, 4):
make_sized_file(os.path.join(video, f"clip-{index:02d}.mp4"), 24 + index * 6)
music = os.path.join(root, "Music")
os.makedirs(music, exist_ok=True)
for index in range(1, 6):
make_sized_file(os.path.join(music, f"track-{index:02d}.mp3"), 4 + index)
def make_archive(root):
folder = os.path.join(root, "Backups")
os.makedirs(folder, exist_ok=True)
with zipfile.ZipFile(os.path.join(folder, "config-backup.zip"), "w", zipfile.ZIP_DEFLATED) as bundle:
bundle.writestr("settings.json", '{\n "theme": "system",\n "sort": "name"\n}\n')
bundle.writestr("bookmarks.txt", "Photos\nDocuments\nBackups\n")
bundle.writestr("notes/readme.txt", "Synthetic contents.\n")
def make_downloads(root):
folder = os.path.join(root, "Download")
os.makedirs(folder, exist_ok=True)
write(os.path.join(folder, "welcome.txt"), "Generated by tools/make-demo-library.py\n")
make_sized_file(os.path.join(folder, "installer.bin"), 18)
def main():
repo = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
root = sys.argv[1] if len(sys.argv) > 1 else os.path.join(repo, "demo-files")
if os.path.isdir(root):
shutil.rmtree(root)
os.makedirs(root)
make_photos(root)
make_text_files(root)
make_media(root)
make_archive(root)
make_downloads(root)
total = 0
for base, _, names in os.walk(root):
for name in names:
total += os.path.getsize(os.path.join(base, name))
print(f"Wrote {root} ({total / (1024 * 1024):.1f} MB)")
if __name__ == "__main__":
main()