Skip to content

Commit

Permalink
feat: init img convert & compress for gallery view
Browse files Browse the repository at this point in the history
- Add `pillow` dependency for image conversion and compression (by running `poetry add pillow`);
- Create a new Django management commands in `download_imgs.py`. By running `python manage.py download_imgs`, it will: (1) download all original instrument images (one image for each instrument) from wikimedia urls; (2) convert original images to .png format and store them on UMIL server; (3) compress original images and store the smaller version as thumbnails on UMIL server;
- Modify `import_instruments.py` command: replace the old image urls with current image path;
- Add `thumbnail` field in `Instrument` model;
- Update the `migrations/0001_initial.py` automatically by running `python manage.py makemigrations`;
- In gallery view HTMLs, replace the old image url with current thumbnail url;

Refs: #138
  • Loading branch information
kunfang98927 committed Aug 12, 2024
1 parent fc6d596 commit be6e90f
Show file tree
Hide file tree
Showing 9 changed files with 995 additions and 791 deletions.
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
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


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)
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:
print(f"Error processing {url}: {e}")

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"

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
)
12 changes: 11 additions & 1 deletion web-app/django/VIM/apps/instruments/migrations/0001_initial.py
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

0 comments on commit be6e90f

Please sign in to comment.