Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
5b0b30d69c | |||
9022facac5 | |||
07725c99b4 | |||
05a184fcf7 |
@ -3,9 +3,11 @@ name: Docker Build and Publish
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
tags: [ 'v*' ]
|
||||
tags: [ 'v*.*.*' ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@ -20,11 +22,11 @@ jobs:
|
||||
with:
|
||||
images: git.dws.rip/${{ github.repository }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
|
||||
type=semver,pattern={{version}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
type=semver,pattern={{major}}.{{minor}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
type=sha,format=long
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=sha
|
||||
|
||||
- name: Login to Gitea Container Registry
|
||||
uses: docker/login-action@v2
|
||||
|
10
README.md
10
README.md
@ -114,4 +114,12 @@ spectra/
|
||||
|
||||
- `FLASK_ENV`: Set to 'production' in production
|
||||
- `WORKERS`: Number of Gunicorn workers (default: 4)
|
||||
- `PORT`: Override default port (default: 5000)
|
||||
- `PORT`: Override default port (default: 5000)
|
||||
|
||||
## Release Process
|
||||
|
||||
To create a release:
|
||||
- Create and push a tag: `git tag v1.0.0 && git push origin v1.0.0`
|
||||
- Create a release in Gitea UI using that tag
|
||||
- The workflow will build and push the Docker image with appropriate version tags
|
||||
- The Docker image will be available at: `git.dws.rip/your-repo/image:v1.0.0`
|
||||
|
51
app.py
51
app.py
@ -289,7 +289,7 @@ def get_images():
|
||||
images = []
|
||||
for photo in photos:
|
||||
factor = random.randint(2, 3)
|
||||
if photo.height < 4000 or photo.width < 4000:
|
||||
if photo.height < 4000 and photo.width < 4000:
|
||||
factor = 1
|
||||
if photo.orientation == 6 or photo.orientation == 8:
|
||||
width, height = photo.height, photo.width
|
||||
@ -461,17 +461,22 @@ def admin_upload():
|
||||
file_path = os.path.join(app.config["UPLOAD_FOLDER"], filename)
|
||||
file.save(file_path)
|
||||
|
||||
# Extract EXIF data
|
||||
exif = None
|
||||
# Extract EXIF data with error handling
|
||||
exif = {}
|
||||
exifraw = None
|
||||
with Image.open(file_path) as img:
|
||||
exifraw = img.info["exif"]
|
||||
width, height = img.size
|
||||
exif = {
|
||||
ExifTags.TAGS[k]: v
|
||||
for k, v in img._getexif().items()
|
||||
if k in ExifTags.TAGS
|
||||
}
|
||||
width = height = 0
|
||||
try:
|
||||
with Image.open(file_path) as img:
|
||||
width, height = img.size
|
||||
if hasattr(img, '_getexif') and img._getexif() is not None:
|
||||
exifraw = img.info.get("exif")
|
||||
exif = {
|
||||
ExifTags.TAGS[k]: v
|
||||
for k, v in img._getexif().items()
|
||||
if k in ExifTags.TAGS
|
||||
}
|
||||
except Exception as e:
|
||||
logger.warning(f"Error reading EXIF data for {filename}: {str(e)}")
|
||||
|
||||
# Generate a unique key for the image
|
||||
unique_key = hashlib.sha256(
|
||||
@ -489,25 +494,25 @@ def admin_upload():
|
||||
# Generate thumbnails
|
||||
generate_thumbnails(filename)
|
||||
|
||||
# Get image dimensions
|
||||
with Image.open(file_path) as img:
|
||||
width, height = img.size
|
||||
# Handle exposure time with error handling
|
||||
try:
|
||||
exposure_time = exif.get("ExposureTime", 0)
|
||||
if isinstance(exposure_time, tuple):
|
||||
exposure_fraction = f"{exposure_time[0]}/{exposure_time[1]}"
|
||||
else:
|
||||
exposure_fraction = f"1/{int(1/float(exposure_time))}" if exposure_time else "0"
|
||||
except (TypeError, ZeroDivisionError):
|
||||
exposure_fraction = "0"
|
||||
|
||||
exposure_time = exif["ExposureTime"]
|
||||
if isinstance(exposure_time, tuple):
|
||||
exposure_fraction = f"{exposure_time[0]}/{exposure_time[1]}"
|
||||
else:
|
||||
exposure_fraction = f"1/{int(1/float(exposure_time))}"
|
||||
|
||||
# Create database entry
|
||||
# Create database entry with safe defaults
|
||||
db_session = DBSession()
|
||||
new_photo = Photo(
|
||||
input_filename=filename,
|
||||
thumbnail_filename=f"{os.path.splitext(filename)[0]}/256_{filename}",
|
||||
focal_length=str(
|
||||
exif.get("FocalLengthIn35mmFilm", exif.get("FocalLength", ""))
|
||||
exif.get("FocalLengthIn35mmFilm", exif.get("FocalLength", "0"))
|
||||
),
|
||||
aperture=str(exif.get("FNumber", "")),
|
||||
aperture=str(exif.get("FNumber", "0")),
|
||||
shutter_speed=exposure_fraction,
|
||||
date_taken=datetime.strptime(
|
||||
str(exif.get("DateTime", "1970:01:01 00:00:00")), "%Y:%m:%d %H:%M:%S"
|
||||
|
@ -203,8 +203,8 @@
|
||||
<td class="editable" data-field="iso">{{ photo.iso }}</td>
|
||||
<td>{{ photo.width }}x{{ photo.height }}</td>
|
||||
<td>
|
||||
<button onclick="saveChanges(this)">Save</button>
|
||||
<button onclick="deletePhoto(this)" class="delete-btn">Delete</button>
|
||||
<button id="save-btn">Save</button>
|
||||
<button class="delete-btn" id="delete-btn">Delete</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
@ -241,8 +241,11 @@
|
||||
<input type="text" id="about.location" name="about.location" value="{{ config.about.location }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="about.profile_image">Profile Image Path:</label>
|
||||
<input type="text" id="about.profile_image" name="about.profile_image" value="{{ config.about.profile_image }}">
|
||||
<label for="about.profile_image">Profile Image:</label>
|
||||
<div style="display: flex; align-items: center; gap: 1rem;">
|
||||
<img id="profile-preview" src="/static/profile.jpeg" alt="Profile" style="width: 100px; height: 100px; object-fit: cover; border-radius: 50%;">
|
||||
<input type="file" id="profile_image_upload" accept="image/jpeg,image/png" style="flex: 1;">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="about.bio">Bio (Markdown):</label>
|
||||
@ -335,11 +338,38 @@
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('delete-btn').addEventListener('click', deletePhoto);
|
||||
document.getElementById('save-btn').addEventListener('click', saveChanges);
|
||||
|
||||
document.getElementById('profile_image_upload').addEventListener('change', async (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('profile_image', file);
|
||||
|
||||
try {
|
||||
const response = await fetch('/admin/upload_profile', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
document.getElementById('profile-preview').src = '/static/profile.jpeg?' + new Date().getTime();
|
||||
} else {
|
||||
alert('Error uploading profile image: ' + result.error);
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Error uploading profile image: ' + error);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('configForm').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = {};
|
||||
const inputs = e.target.querySelectorAll('input, textarea');
|
||||
const inputs = e.target.querySelectorAll('input:not([type="file"]), textarea');
|
||||
|
||||
inputs.forEach(input => {
|
||||
formData[input.name] = input.value;
|
||||
|
Loading…
Reference in New Issue
Block a user