Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ env/
exports/

.editorconfig
.env
.env.example
.gitignore
.markdownlint.json
Expand Down
3 changes: 3 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ indent_size = 2

[*.{toml,yml,yaml,json,ini}]
indent_size = 2

[site.webmanifest]
indent_size = 2
10 changes: 1 addition & 9 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,12 +1,4 @@
HOST=0.0.0.0
PORT=5000

FLASK_APP=bereal/server.py
FLASK_ENV=development

SECRET_KEY=hello

HOST=redis
HOST=localhost
PORT=5000

REDIS_HOST=redis
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

*.db
*.rdb
*.log*

content/**/**
!content/.gitkeep
Expand Down
1 change: 1 addition & 0 deletions .vscode/ltex.dictionary.en-US.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ processForm
otp
localStorage
getItem
ubc
7 changes: 4 additions & 3 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@
"*.mako": "python",
"*.conf": "ini",
"*.rdb": "sql",
"*.local": "plaintext"
"*.local": "plaintext",
"log.log*": "plaintext",
"site.webmanifest": "json",
},
"files.insertFinalNewline": true,
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll": "explicit",
// TODO(michaelfromyeg): make this happen for everything except CSS
"source.organizeImports": "never"
"source.organizeImports": "explicit"
},
"python.terminal.activateEnvironment": true,
"python.terminal.activateEnvInCurrentTerminal": true,
Expand Down
28 changes: 19 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,21 @@ client:
@cd client && npm start

build:
@echo "Building the server..."
@docker-compose -f docker-compose.local.yml build
@echo "Building the server image..."
@docker build -t michaelfromyeg/bereal_wrapped -f docker/Dockerfile.server .
@docker push michaelfromyeg/bereal_wrapped

up:
@echo "Booting up the server..."
@docker-compose -f docker-compose.local.yml up -d
@docker stack deploy -c docker-stack.local.yml bereal-wrapped

logs:
@echo "Showing the server logs..."
@docker-compose -f docker-compose.local.yml logs -f
@docker service logs -f bereal-wrapped_web

down:
@echo "Shutting down the server..."
@docker-compose -f docker-compose.local.yml down

kill:
@echo "Killing the server..."
@docker-compose -f docker-compose.local.yml kill
@docker stack rm bereal-wrapped

start-redis:
@echo "Booting up Redis..."
Expand All @@ -38,14 +35,20 @@ stop-redis:

celery:
@echo "Booting up Celery..."
@export FLASK_APP=bereal.server
@export FLASK_ENV=development--non-docker
@celery -A bereal.celery worker --loglevel=DEBUG --logfile=celery.log -E

flower:
@echo "Booting up Flower..."
@export FLASK_APP=bereal.server
@export FLASK_ENV=development--non-docker
@celery -A bereal.celery flower --address=0.0.0.0 --inspect --enable-events --loglevel=DEBUG --logfile=flower.log

server:
@echo "Booting up the server..."
@export FLASK_APP=bereal.server
@export FLASK_ENV=development--non-docker
@python -m bereal.server

cli:
Expand All @@ -59,3 +62,10 @@ typecheck:
format:
@echo "Formatting the code..."
@ruff check bereal && ruff format bereal

clean:
rm *.log*
rm *.rdb
rm bereal/*.db
rm exports/*.mp4
rm -rf content/*/
14 changes: 9 additions & 5 deletions bereal/bereal.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,13 @@

import os
from datetime import datetime
from typing import Any
from typing import Any, Literal, TypedDict

import requests as r

from .logger import logger
from .utils import BASE_URL, CONTENT_PATH, TIMEOUT, str2datetime

from typing import TypedDict, Literal


class MediaInfo(TypedDict):
"""
Expand Down Expand Up @@ -139,8 +137,14 @@ def download_image(date_str: str, url: str, base_path: str) -> None:
logger.debug("Invalid date: %s", date_str)
return None

# Extracting the image name from the URL
image_name = date_str + "_" + url.split("/")[-1]
# Extracting the image name from the URL; will be the last "thing" in this/that/other.xyz
image_path = url.split("/")[-1]

# if image_path.lower().endswith(".webp"):
# logger.warning("Skipping webp image: %s, currently not supported", image_path)
# return None

image_name = date_str + "_" + image_path

with open(os.path.join(base_path, image_name), "wb") as img_file:
img_response = r.get(url, timeout=10)
Expand Down
6 changes: 4 additions & 2 deletions bereal/celery.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ def make_celery(app_name=__name__, broker=f"redis://{REDIS_HOST}:{REDIS_PORT}/0"


@bcelery.task(time_limit=1200)
def make_video(token: str, bereal_token: str, phone: str, year: str, song_path: str, mode: Mode) -> str:
def make_video(
token: str, bereal_token: str, phone: str, year: str, display_date: bool, song_path: str, mode: Mode
) -> str:
"""
Creating a video takes about ~15 min. This is a work-in-progress!

Expand All @@ -44,7 +46,7 @@ def make_video(token: str, bereal_token: str, phone: str, year: str, song_path:

logger.info("Creating images for %s...", video_file)
try:
image_folder = create_images(phone, year)
image_folder = create_images(phone, year, display_date)
except Exception as e:
logger.error("Failed to create images: %s", e)
gc.collect()
Expand Down
57 changes: 23 additions & 34 deletions bereal/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ def process_image(
primary_folder: str,
secondary_folder: str,
output_folder: str,
display_date: bool = False,
) -> None:
"""
Combine the primary image with the secondary image, and save the result in the output folder.
Expand All @@ -24,82 +25,70 @@ def process_image(
offset = 50
text_opacity = 150

# Extract prefix from primary filename
primary_prefix = primary_filename.split("_")[0]

# Check if there's a corresponding file in the secondary folder with the same prefix
secondary_files = [file for file in os.listdir(secondary_folder) if file.startswith(primary_prefix)]

if not secondary_files:
return None

# Use the first matching file in the secondary folder
secondary_filename = secondary_files[0]

primary_path = os.path.join(primary_folder, primary_filename)
secondary_path = os.path.join(secondary_folder, secondary_filename)

# Load primary and secondary images
primary_image = Image.open(primary_path)
secondary_image = Image.open(secondary_path)
source = Image.open(os.path.join(os.getcwd(), OUTLINE_PATH))
outline_image = Image.open(OUTLINE_PATH)

# TODO(michaelfromyeg): hack to fix alpha bug; remove after properly addressed
primary_image = primary_image.convert("RGBA")
secondary_image = secondary_image.convert("RGBA")
source = source.convert("RGBA")
outline_image = outline_image.convert("RGBA")

# Create border around secondary image
secondary_image = ImageChops.multiply(source, secondary_image)
secondary_image = ImageChops.multiply(outline_image, secondary_image)

# Resize secondary image to fraction the size of the primary image
width, height = primary_image.size
new_size = (width // 3, height // 3)
secondary_image = secondary_image.resize(new_size)

# Overlay secondary image on top-left corner of primary image
primary_image.paste(secondary_image, (10, 10), secondary_image)

width, height = primary_image.size
draw = ImageDraw.Draw(primary_image)

# font file is assumed to exist under static/
font_path = os.path.join(FONT_BASE_PATH, "Inter-Bold.ttf")
font = ImageFont.truetype(font_path, font_size)

text_bbox = draw.textbbox((0, 0), primary_prefix, font=font)
if display_date:
font_path = os.path.join(FONT_BASE_PATH, "Inter-Bold.ttf")
font = ImageFont.truetype(font_path, font_size)

# Calculate the position to center the text
x = (width - text_bbox[2]) // 2
y = (height - text_bbox[3]) - offset
text_bbox = draw.textbbox((0, 0), primary_prefix, font=font)

# Calculate the size of the rectangle to fill the text_bbox
rect_width = text_bbox[2] + 20 # Add some padding
rect_height = text_bbox[3] + 20 # Add some padding
x = (width - text_bbox[2]) // 2
y = (height - text_bbox[3]) - offset

# Draw a semi-transparent filled rectangle as the background
draw.rectangle(
((x - 30, y - 15), (x + rect_width + 10, y + rect_height + 10)),
fill=(0, 0, 0, text_opacity),
)
rect_width = text_bbox[2] + 20
rect_height = text_bbox[3] + 20

# Draw the text on the image
draw.text((x, y), primary_prefix, font=font, fill="white")
# Save the modified image
draw.text((x, y), primary_prefix, font=font, fill="white")
draw.rectangle(
((x - 30, y - 15), (x + rect_width + 10, y + rect_height + 10)),
fill=(0, 0, 0, text_opacity),
)

# Save the result in the output folder
output_path = os.path.join(output_folder, f"combined_{primary_filename}")

# ensure the photo is jpg ready
# TODO(michaelfromyeg): this is a hack; I had some issues with the alpha channel showing up occasionally
primary_image = primary_image.convert("RGB")

primary_image.save(output_path, quality=IMAGE_QUALITY)

logger.debug("Combined image saved at %s", output_path)

return None


def create_images(
phone: str,
year: str,
display_date: bool,
) -> str:
"""
Put secondary images on top of primary images.
Expand All @@ -124,7 +113,7 @@ def create_images(
# specifically, "AssertionError: daemonic processes are not allowed to have children"

for primary_filename in primary_filenames:
process_image(primary_filename, primary_folder, secondary_folder, output_folder)
process_image(primary_filename, primary_folder, secondary_folder, output_folder, display_date)

# Use multiprocessing to process images in parallel
# processes = max(1, multiprocessing.cpu_count() - 2)
Expand Down
18 changes: 8 additions & 10 deletions bereal/send.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
For now, only phone. Eventually, consider e-mail.
"""

from .utils import FLASK_ENV, TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_PHONE_NUMBER
from .logger import logger

from twilio.rest import Client

from .logger import logger
from .utils import FLASK_ENV, TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_PHONE_NUMBER


def sms(phone: str, link: str) -> None:
"""
Expand All @@ -17,14 +17,12 @@ def sms(phone: str, link: str) -> None:
try:
client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)

if FLASK_ENV == "development":
logger.info("Skipping SMS in development mode")
return

message_body = f"Here is the link to your BeReal Wrapped!\n\n{link}"
message = client.messages.create(body=message_body, from_=TWILIO_PHONE_NUMBER, to=phone)

logger.info("Sent message to %s: %s", phone, message.sid)
if FLASK_ENV == "development" or FLASK_ENV == "development--non-docker":
logger.debug("Skipping SMS in development mode; would send %s", message_body)
else:
message = client.messages.create(body=message_body, from_=TWILIO_PHONE_NUMBER, to=phone)
logger.info("Sent message %s to %s", message.sid, phone)
except Exception as e:
logger.error("Failed to send SMS: %s", e)
pass
Loading
Loading