Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: init img convert & compress for gallery view #139

Merged
merged 3 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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,653 changes: 875 additions & 778 deletions poetry.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ django = "4.2.5"
psycopg = "3.1.10"
requests = "2.31.0"
gunicorn = "21.2.0"
pillow = "^10.4.0"


[tool.poetry.group.dev.dependencies]
Expand Down
dchiller marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import csv
import os
import requests
from PIL import Image
from io import BytesIO
from django.core.management.base import BaseCommand
dchiller marked this conversation as resolved.
Show resolved Hide resolved


class ImageDownloader:
def __init__(self, user_agent, output_dir):
self.headers = {"User-Agent": user_agent}
self.original_img_dir = os.path.join(output_dir, "original")
self.thumbnail_dir = os.path.join(output_dir, "thumbnail")
os.makedirs(self.original_img_dir, exist_ok=True)
os.makedirs(self.thumbnail_dir, exist_ok=True)

def download_image_as_png(self, url, save_path):
try:
response = requests.get(url, stream=True, headers=self.headers)
dchiller marked this conversation as resolved.
Show resolved Hide resolved
response.raise_for_status() # Raise an HTTPError for bad responses
self._save_image_as_png(response.content, save_path)
print(f"Downloaded {url} to {save_path}")
except requests.RequestException as e:
print(f"Failed to download {url}: {e}")
except Exception as e:
dchiller marked this conversation as resolved.
Show resolved Hide resolved
print(f"Error processing {url}: {e}")
dchiller marked this conversation as resolved.
Show resolved Hide resolved

def _save_image_as_png(self, img_content, save_path):
img = Image.open(BytesIO(img_content))
img.save(save_path, "PNG")

def create_thumbnail(self, image_path, thumbnail_path, compression_ratio=0.35):
try:
with Image.open(image_path) as original_img:
new_size = (
int(original_img.width * compression_ratio),
int(original_img.height * compression_ratio),
)
original_img.thumbnail(new_size)
original_img.save(thumbnail_path, "PNG")
print(f"Created thumbnail for {image_path}")
except Exception as e:
print(f"Error creating thumbnail for {image_path}: {e}")

def process_images(self, csv_file_path):
with open(csv_file_path, encoding="utf-8-sig") as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
image_url = row["image"]
instrument_wikidata_id = row["instrument"].split("/")[-1]
save_path_png = os.path.join(
self.original_img_dir, f"{instrument_wikidata_id}.png"
)
thumbnail_path = os.path.join(
self.thumbnail_dir, f"{instrument_wikidata_id}.png"
)

if not os.path.exists(save_path_png):
self.download_image_as_png(image_url, save_path_png)

if not os.path.exists(thumbnail_path):
self.create_thumbnail(save_path_png, thumbnail_path)


class Command(BaseCommand):
help = "Download images and create thumbnails for instruments"

def handle(self, *args, **options):
user_agent = (
"UMIL/0.1.0 (https://vim.simssa.ca/; https://ddmal.music.mcgill.ca/)"
)
output_dir = "VIM/apps/instruments/static/instruments/images/instrument_imgs"
csv_file_path = "startup_data/vim_instruments_with_images-15sept.csv"
dchiller marked this conversation as resolved.
Show resolved Hide resolved

downloader = ImageDownloader(user_agent, output_dir)
downloader.process_images(csv_file_path)
print("Images downloaded and thumbnails created")
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,17 @@ def get_instrument_data(self, instrument_ids: list[str]) -> list[dict]:
]
return instrument_data

def create_database_objects(self, instrument_attrs: dict, ins_img_url: str) -> None:
def create_database_objects(
self, instrument_attrs: dict, original_img_path: str, thumbnail_img_path: str
) -> None:
"""
Given a dictionary of instrument attributes and a url to an instrument image,
create the corresponding database objects.

instrument_attrs [dict]: Dictionary of instrument attributes. See
parse_instrument_data for details.
ins_img_url [str]: URL of instrument image
original_img_path [str]: Path to the original instrument image
thumbnail_img_path [str]: Path to the thumbnail of the instrument image
"""
ins_names = instrument_attrs.pop("ins_names")
instrument = Instrument.objects.create(**instrument_attrs)
Expand All @@ -105,10 +108,17 @@ def create_database_objects(self, instrument_attrs: dict, ins_img_url: str) -> N
img_obj = AVResource.objects.create(
instrument=instrument,
type="image",
format=ins_img_url.split(".")[-1],
url=ins_img_url,
format=original_img_path.split(".")[-1],
url=original_img_path,
)
instrument.default_image = img_obj
thumbnail_obj = AVResource.objects.create(
instrument=instrument,
type="image",
format=thumbnail_img_path.split(".")[-1],
url=thumbnail_img_path,
)
instrument.thumbnail = thumbnail_obj
instrument.save()

def handle(self, *args, **options) -> None:
Expand All @@ -125,8 +135,10 @@ def handle(self, *args, **options) -> None:
for ins in instrument_list[ins_i : ins_i + 50]
]
ins_data: list[dict] = self.get_instrument_data(ins_ids_subset)
ins_imgs_subset: list[str] = [
ins["image"] for ins in instrument_list[ins_i : ins_i + 50]
]
for instrument_attrs, ins_img_url in zip(ins_data, ins_imgs_subset):
self.create_database_objects(instrument_attrs, ins_img_url)
for instrument_attrs, ins_id in zip(ins_data, ins_ids_subset):
img_dir = "../../static/instruments/images/instrument_imgs"
original_img_path = f"{img_dir}/original/{ins_id}.png"
thumbnail_img_path = f"{img_dir}/thumbnail/{ins_id}.png"
self.create_database_objects(
instrument_attrs, original_img_path, thumbnail_img_path
)
dchiller marked this conversation as resolved.
Show resolved Hide resolved
12 changes: 11 additions & 1 deletion web-app/django/VIM/apps/instruments/migrations/0001_initial.py
dchiller marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 4.2.5 on 2023-09-27 18:47
# Generated by Django 4.2.5 on 2024-08-12 14:23

from django.db import migrations, models
import django.db.models.deletion
Expand Down Expand Up @@ -102,6 +102,16 @@ class Migration(migrations.Migration):
to="instruments.avresource",
),
),
(
"thumbnail",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="thumbnail_of",
to="instruments.avresource",
),
),
],
),
migrations.CreateModel(
Expand Down
7 changes: 7 additions & 0 deletions web-app/django/VIM/apps/instruments/models/instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ class Instrument(models.Model):
null=True,
related_name="default_image_of",
)
thumbnail = models.ForeignKey(
"AVResource",
on_delete=models.PROTECT,
blank=True,
null=True,
related_name="thumbnail_of",
)
hornbostel_sachs_class = models.CharField(
max_length=50, blank=True, help_text="Hornbostel-Sachs classification"
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<div class="row g-0 p-2">
<div class="col-md-2 align-items-center list-img-container">
<a href="#" class="text-decoration-none">
<img src="{{ instrument.avresource_set.first.url }}" class="img-fluid rounded" alt="instrument image" onerror="this.onerror=null;this.src='{% static "instruments/images/no-image.svg" %}';" />
<img src="{{ instrument.thumbnail.url }}" class="img-fluid rounded" alt="instrument thumbnail" onerror="this.onerror=null;this.src='{% static "instruments/images/no-image.svg" %}';" />
</a>
</div>
<div class="col-md-10 card-body pb-2 pt-0">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
class="text-decoration-none"
target="_blank">
<div class="card mb-3">
<img src="{{ instrument.avresource_set.first.url }}" class="card-img-top rounded p-2" alt="instrument image" onerror="this.onerror=null;this.src='{% static "instruments/images/no-image.svg" %}';" />
<img src="{{ instrument.thumbnail.url }}" class="card-img-top rounded p-2" alt="instrument thumbnail" onerror="this.onerror=null;this.src='{% static "instruments/images/no-image.svg" %}';" />
<div class="card-body pb-0 pt-0">
<p class="card-title text-center notranslate ">
{% for instrumentname in instrument.instrumentname_set.all %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
target="_blank">
<div class="card mb-3">
<div class="square-box">
<img src="{{ instrument.avresource_set.first.url }}" class="card-img-top rounded p-2 img-fluid" alt="instrument image" onerror="this.onerror=null;this.src='{% static "instruments/images/no-image.svg" %}';" />
<img src="{{ instrument.thumbnail.url }}" class="card-img-top rounded p-2 img-fluid" alt="instrument thumbnail" onerror="this.onerror=null;this.src='{% static "instruments/images/no-image.svg" %}';" />
</div>
<div class="card-body pb-0 pt-0">
<p class="card-title text-center notranslate">
Expand Down