Big rewrite
This commit is contained in:
38
Dockerfile
38
Dockerfile
@@ -12,10 +12,48 @@ RUN apt-get update && apt-get install -y \
|
|||||||
fonts-dejavu-extra \
|
fonts-dejavu-extra \
|
||||||
fonts-liberation \
|
fonts-liberation \
|
||||||
fonts-liberation2 \
|
fonts-liberation2 \
|
||||||
|
fonts-noto \
|
||||||
|
fonts-freefont-ttf \
|
||||||
fontconfig \
|
fontconfig \
|
||||||
|
wget \
|
||||||
|
unzip \
|
||||||
|
git \
|
||||||
&& fc-cache -f -v \
|
&& fc-cache -f -v \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Download and install select Google Fonts from GitHub
|
||||||
|
RUN mkdir -p /usr/share/fonts/truetype/google-fonts && \
|
||||||
|
cd /tmp && \
|
||||||
|
# Roboto from official release\
|
||||||
|
wget -q https://github.com/google/roboto/releases/download/v2.138/roboto-unhinted.zip && \
|
||||||
|
unzip -q roboto-unhinted.zip -d roboto && \
|
||||||
|
find roboto -name "*.ttf" -path "*/Roboto-Bold.ttf" -exec cp {} /usr/share/fonts/truetype/google-fonts/ \; && \
|
||||||
|
find roboto -name "*.ttf" -path "*/Roboto-Regular.ttf" -exec cp {} /usr/share/fonts/truetype/google-fonts/ \; && \
|
||||||
|
find roboto -name "*.ttf" -path "*/Roboto-Black.ttf" -exec cp {} /usr/share/fonts/truetype/google-fonts/ \; && \
|
||||||
|
# Clone Google Fonts repo for other fonts (shallow clone)\
|
||||||
|
git clone --depth 1 --filter=blob:none --sparse https://github.com/google/fonts.git && \
|
||||||
|
cd fonts && \
|
||||||
|
git sparse-checkout set ofl/opensans ofl/montserrat ofl/oswald ofl/raleway ofl/lato ofl/poppins ofl/anton ofl/bebasneue ofl/ptsans ofl/nunito && \
|
||||||
|
# Copy specific font files\
|
||||||
|
find ofl/opensans -name "OpenSans-Bold.ttf" -o -name "OpenSans-Regular.ttf" -o -name "OpenSans-ExtraBold.ttf" | xargs -I {} cp {} /usr/share/fonts/truetype/google-fonts/ 2>/dev/null || true && \
|
||||||
|
find ofl/opensans -name "OpenSans*\[wght\].ttf" -exec cp {} /usr/share/fonts/truetype/google-fonts/OpenSans-Variable.ttf \; 2>/dev/null || true && \
|
||||||
|
find ofl/montserrat -name "Montserrat-Bold.ttf" -o -name "Montserrat-Regular.ttf" -o -name "Montserrat-Black.ttf" | xargs -I {} cp {} /usr/share/fonts/truetype/google-fonts/ 2>/dev/null || true && \
|
||||||
|
find ofl/montserrat -name "Montserrat*\[wght\].ttf" -exec cp {} /usr/share/fonts/truetype/google-fonts/Montserrat-Variable.ttf \; 2>/dev/null || true && \
|
||||||
|
find ofl/oswald -name "Oswald-Bold.ttf" -o -name "Oswald-Regular.ttf" | xargs -I {} cp {} /usr/share/fonts/truetype/google-fonts/ 2>/dev/null || true && \
|
||||||
|
find ofl/oswald -name "Oswald*\[wght\].ttf" -exec cp {} /usr/share/fonts/truetype/google-fonts/Oswald-Variable.ttf \; 2>/dev/null || true && \
|
||||||
|
find ofl/raleway -name "Raleway-Bold.ttf" -o -name "Raleway-Regular.ttf" -o -name "Raleway-Black.ttf" | xargs -I {} cp {} /usr/share/fonts/truetype/google-fonts/ 2>/dev/null || true && \
|
||||||
|
find ofl/raleway -name "Raleway*\[wght\].ttf" -exec cp {} /usr/share/fonts/truetype/google-fonts/Raleway-Variable.ttf \; 2>/dev/null || true && \
|
||||||
|
find ofl/lato -name "Lato-Bold.ttf" -o -name "Lato-Regular.ttf" -o -name "Lato-Black.ttf" | xargs -I {} cp {} /usr/share/fonts/truetype/google-fonts/ 2>/dev/null || true && \
|
||||||
|
find ofl/poppins -name "Poppins-Bold.ttf" -o -name "Poppins-Regular.ttf" -o -name "Poppins-Black.ttf" | xargs -I {} cp {} /usr/share/fonts/truetype/google-fonts/ 2>/dev/null || true && \
|
||||||
|
find ofl/anton -name "Anton-Regular.ttf" | xargs -I {} cp {} /usr/share/fonts/truetype/google-fonts/ 2>/dev/null || true && \
|
||||||
|
find ofl/bebasneue -name "BebasNeue-Regular.ttf" | xargs -I {} cp {} /usr/share/fonts/truetype/google-fonts/ 2>/dev/null || true && \
|
||||||
|
find ofl/ptsans -name "PTSans-Bold.ttf" -o -name "PTSans-Regular.ttf" | xargs -I {} cp {} /usr/share/fonts/truetype/google-fonts/ 2>/dev/null || true && \
|
||||||
|
find ofl/nunito -name "Nunito-Bold.ttf" -o -name "Nunito-Regular.ttf" -o -name "Nunito-Black.ttf" | xargs -I {} cp {} /usr/share/fonts/truetype/google-fonts/ 2>/dev/null || true && \
|
||||||
|
find ofl/nunito -name "Nunito*\[wght\].ttf" -exec cp {} /usr/share/fonts/truetype/google-fonts/Nunito-Variable.ttf \; 2>/dev/null || true && \
|
||||||
|
# Clean up\
|
||||||
|
cd /tmp && rm -rf fonts roboto *.zip && \
|
||||||
|
fc-cache -f -v
|
||||||
|
|
||||||
# Set working directory
|
# Set working directory
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|||||||
33
README.md
33
README.md
@@ -13,6 +13,39 @@ A Python web application for adding custom text to TV station and network logos.
|
|||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
|
### Using Python Directly
|
||||||
|
|
||||||
|
If you prefer to run the application without Docker:
|
||||||
|
|
||||||
|
1. **Clone the repository (or download the files):**
|
||||||
|
```bash
|
||||||
|
git clone <repository-url>
|
||||||
|
cd logo-txt
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Install Python dependencies:**
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
Or with a virtual environment (recommended):
|
||||||
|
```bash
|
||||||
|
python3 -m venv venv
|
||||||
|
source venv/bin/activate # On Windows: venv\Scripts\activate
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Run the application:**
|
||||||
|
```bash
|
||||||
|
python app.py
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Access the application:**
|
||||||
|
Open your browser and navigate to `http://localhost:5001`
|
||||||
|
|
||||||
|
5. **Stop the application:**
|
||||||
|
Press `Ctrl+C` in the terminal
|
||||||
|
|
||||||
### Using Docker Compose (Recommended)
|
### Using Docker Compose (Recommended)
|
||||||
|
|
||||||
1. **Create a `docker-compose.yml` file:**
|
1. **Create a `docker-compose.yml` file:**
|
||||||
|
|||||||
323
app.py
323
app.py
@@ -99,45 +99,157 @@ def detect_font_from_image(img):
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_font(font_size, detected_font_path=None):
|
def get_available_fonts():
|
||||||
|
"""
|
||||||
|
Get list of available fonts on the system with their paths and display names
|
||||||
|
Works on Linux (Docker), macOS, and Windows
|
||||||
|
"""
|
||||||
|
font_list = [
|
||||||
|
# DejaVu fonts (fonts-dejavu package) - Sans-serif
|
||||||
|
{'path': '/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf', 'name': 'DejaVu Sans Bold'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', 'name': 'DejaVu Sans'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/dejavu/DejaVuSans-ExtraLight.ttf', 'name': 'DejaVu Sans ExtraLight'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/dejavu/DejaVuSansCondensed-Bold.ttf', 'name': 'DejaVu Sans Condensed Bold'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/dejavu/DejaVuSansCondensed.ttf', 'name': 'DejaVu Sans Condensed'},
|
||||||
|
# DejaVu fonts - Serif
|
||||||
|
{'path': '/usr/share/fonts/truetype/dejavu/DejaVuSerif-Bold.ttf', 'name': 'DejaVu Serif Bold'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/dejavu/DejaVuSerif.ttf', 'name': 'DejaVu Serif'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/dejavu/DejaVuSerifCondensed-Bold.ttf', 'name': 'DejaVu Serif Condensed Bold'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/dejavu/DejaVuSerifCondensed.ttf', 'name': 'DejaVu Serif Condensed'},
|
||||||
|
# DejaVu fonts - Monospace
|
||||||
|
{'path': '/usr/share/fonts/truetype/dejavu/DejaVuSansMono-Bold.ttf', 'name': 'DejaVu Sans Mono Bold'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf', 'name': 'DejaVu Sans Mono'},
|
||||||
|
# Liberation fonts (fonts-liberation package) - Sans-serif
|
||||||
|
{'path': '/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf', 'name': 'Liberation Sans Bold'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf', 'name': 'Liberation Sans'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/liberation/LiberationSansNarrow-Bold.ttf', 'name': 'Liberation Sans Narrow Bold'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/liberation/LiberationSansNarrow-Regular.ttf', 'name': 'Liberation Sans Narrow'},
|
||||||
|
# Liberation fonts - Serif
|
||||||
|
{'path': '/usr/share/fonts/truetype/liberation/LiberationSerif-Bold.ttf', 'name': 'Liberation Serif Bold'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/liberation/LiberationSerif-Regular.ttf', 'name': 'Liberation Serif'},
|
||||||
|
# Liberation fonts - Monospace
|
||||||
|
{'path': '/usr/share/fonts/truetype/liberation/LiberationMono-Bold.ttf', 'name': 'Liberation Mono Bold'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/liberation/LiberationMono-Regular.ttf', 'name': 'Liberation Mono'},
|
||||||
|
# Liberation2 fonts (fonts-liberation2 package)
|
||||||
|
{'path': '/usr/share/fonts/truetype/liberation2/LiberationSans-Bold.ttf', 'name': 'Liberation Sans Bold (v2)'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/liberation2/LiberationSans-Regular.ttf', 'name': 'Liberation Sans (v2)'},
|
||||||
|
# Noto fonts (fonts-noto package) - Sans-serif
|
||||||
|
{'path': '/usr/share/fonts/truetype/noto/NotoSans-Bold.ttf', 'name': 'Noto Sans Bold'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/noto/NotoSans-Regular.ttf', 'name': 'Noto Sans'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/noto/NotoSans-ExtraBold.ttf', 'name': 'Noto Sans ExtraBold'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/noto/NotoSans-Light.ttf', 'name': 'Noto Sans Light'},
|
||||||
|
# Noto fonts - Serif
|
||||||
|
{'path': '/usr/share/fonts/truetype/noto/NotoSerif-Bold.ttf', 'name': 'Noto Serif Bold'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/noto/NotoSerif-Regular.ttf', 'name': 'Noto Serif'},
|
||||||
|
# Noto fonts - Monospace
|
||||||
|
{'path': '/usr/share/fonts/truetype/noto/NotoMono-Regular.ttf', 'name': 'Noto Mono'},
|
||||||
|
# FreeFont (fonts-freefont-ttf package) - Sans-serif
|
||||||
|
{'path': '/usr/share/fonts/truetype/freefont/FreeSansBold.ttf', 'name': 'FreeSans Bold'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/freefont/FreeSans.ttf', 'name': 'FreeSans'},
|
||||||
|
# FreeFont - Serif
|
||||||
|
{'path': '/usr/share/fonts/truetype/freefont/FreeSerifBold.ttf', 'name': 'FreeSerif Bold'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/freefont/FreeSerif.ttf', 'name': 'FreeSerif'},
|
||||||
|
# FreeFont - Monospace
|
||||||
|
{'path': '/usr/share/fonts/truetype/freefont/FreeMonoBold.ttf', 'name': 'FreeMono Bold'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/freefont/FreeMono.ttf', 'name': 'FreeMono'},
|
||||||
|
# Google Fonts - Sans-serif (great for logos)
|
||||||
|
{'path': '/usr/share/fonts/truetype/google-fonts/Roboto-Black.ttf', 'name': 'Roboto Black'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/google-fonts/Roboto-Bold.ttf', 'name': 'Roboto Bold'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/google-fonts/Roboto-Regular.ttf', 'name': 'Roboto'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/google-fonts/OpenSans-ExtraBold.ttf', 'name': 'Open Sans ExtraBold'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/google-fonts/OpenSans-Bold.ttf', 'name': 'Open Sans Bold'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/google-fonts/OpenSans-Regular.ttf', 'name': 'Open Sans'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/google-fonts/Montserrat-Black.ttf', 'name': 'Montserrat Black'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/google-fonts/Montserrat-Bold.ttf', 'name': 'Montserrat Bold'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/google-fonts/Montserrat-Regular.ttf', 'name': 'Montserrat'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/google-fonts/Oswald-Bold.ttf', 'name': 'Oswald Bold'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/google-fonts/Oswald-Regular.ttf', 'name': 'Oswald'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/google-fonts/Raleway-Black.ttf', 'name': 'Raleway Black'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/google-fonts/Raleway-Bold.ttf', 'name': 'Raleway Bold'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/google-fonts/Raleway-Regular.ttf', 'name': 'Raleway'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/google-fonts/Lato-Black.ttf', 'name': 'Lato Black'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/google-fonts/Lato-Bold.ttf', 'name': 'Lato Bold'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/google-fonts/Lato-Regular.ttf', 'name': 'Lato'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/google-fonts/Poppins-Black.ttf', 'name': 'Poppins Black'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/google-fonts/Poppins-Bold.ttf', 'name': 'Poppins Bold'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/google-fonts/Poppins-Regular.ttf', 'name': 'Poppins'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/google-fonts/Anton-Regular.ttf', 'name': 'Anton'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/google-fonts/BebasNeue-Regular.ttf', 'name': 'Bebas Neue'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/google-fonts/PTSans-Bold.ttf', 'name': 'PT Sans Bold'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/google-fonts/PTSans-Regular.ttf', 'name': 'PT Sans'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/google-fonts/Nunito-Black.ttf', 'name': 'Nunito Black'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/google-fonts/Nunito-Bold.ttf', 'name': 'Nunito Bold'},
|
||||||
|
{'path': '/usr/share/fonts/truetype/google-fonts/Nunito-Regular.ttf', 'name': 'Nunito'},
|
||||||
|
|
||||||
|
# macOS system fonts (fallback for running outside Docker on Mac)
|
||||||
|
{'path': '/System/Library/Fonts/Helvetica.ttc', 'name': 'Helvetica'},
|
||||||
|
{'path': '/System/Library/Fonts/Supplemental/Arial.ttf', 'name': 'Arial'},
|
||||||
|
{'path': '/System/Library/Fonts/Supplemental/Arial Bold.ttf', 'name': 'Arial Bold'},
|
||||||
|
{'path': '/System/Library/Fonts/Supplemental/Arial Black.ttf', 'name': 'Arial Black'},
|
||||||
|
{'path': '/System/Library/Fonts/Supplemental/Verdana.ttf', 'name': 'Verdana'},
|
||||||
|
{'path': '/System/Library/Fonts/Supplemental/Verdana Bold.ttf', 'name': 'Verdana Bold'},
|
||||||
|
{'path': '/System/Library/Fonts/Supplemental/Tahoma.ttf', 'name': 'Tahoma'},
|
||||||
|
{'path': '/System/Library/Fonts/Supplemental/Tahoma Bold.ttf', 'name': 'Tahoma Bold'},
|
||||||
|
{'path': '/System/Library/Fonts/Supplemental/Impact.ttf', 'name': 'Impact'},
|
||||||
|
{'path': '/System/Library/Fonts/Supplemental/Times New Roman.ttf', 'name': 'Times New Roman'},
|
||||||
|
{'path': '/System/Library/Fonts/Supplemental/Times New Roman Bold.ttf', 'name': 'Times New Roman Bold'},
|
||||||
|
|
||||||
|
# Windows system fonts (fallback for running outside Docker on Windows)
|
||||||
|
{'path': 'C:\\Windows\\Fonts\\arial.ttf', 'name': 'Arial (Windows)'},
|
||||||
|
{'path': 'C:\\Windows\\Fonts\\arialbd.ttf', 'name': 'Arial Bold (Windows)'},
|
||||||
|
{'path': 'C:\\Windows\\Fonts\\ariblk.ttf', 'name': 'Arial Black (Windows)'},
|
||||||
|
{'path': 'C:\\Windows\\Fonts\\verdana.ttf', 'name': 'Verdana (Windows)'},
|
||||||
|
{'path': 'C:\\Windows\\Fonts\\verdanab.ttf', 'name': 'Verdana Bold (Windows)'},
|
||||||
|
{'path': 'C:\\Windows\\Fonts\\tahoma.ttf', 'name': 'Tahoma (Windows)'},
|
||||||
|
{'path': 'C:\\Windows\\Fonts\\tahomabd.ttf', 'name': 'Tahoma Bold (Windows)'},
|
||||||
|
{'path': 'C:\\Windows\\Fonts\\impact.ttf', 'name': 'Impact (Windows)'},
|
||||||
|
{'path': 'C:\\Windows\\Fonts\\times.ttf', 'name': 'Times New Roman (Windows)'},
|
||||||
|
{'path': 'C:\\Windows\\Fonts\\timesbd.ttf', 'name': 'Times New Roman Bold (Windows)'},
|
||||||
|
]
|
||||||
|
|
||||||
|
# Filter to only fonts that exist on this system
|
||||||
|
available = []
|
||||||
|
seen_names = set()
|
||||||
|
for font in font_list:
|
||||||
|
if os.path.exists(font['path']) and font['name'] not in seen_names:
|
||||||
|
available.append(font)
|
||||||
|
seen_names.add(font['name'])
|
||||||
|
|
||||||
|
# If no fonts found, return a placeholder that will trigger default font usage
|
||||||
|
if not available:
|
||||||
|
available.append({'path': 'default', 'name': 'System Default'})
|
||||||
|
|
||||||
|
return available
|
||||||
|
|
||||||
|
def get_font(font_size, font_path=None, detected_font_path=None):
|
||||||
"""
|
"""
|
||||||
Get the best available font for text rendering
|
Get the best available font for text rendering
|
||||||
|
Falls back to system default if no fonts are available
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
# If a specific font was requested and it exists, use it
|
||||||
|
if font_path and font_path != 'auto' and font_path != 'default' and os.path.exists(font_path):
|
||||||
|
return ImageFont.truetype(font_path, font_size)
|
||||||
|
|
||||||
# If we detected a font from the logo, use it
|
# If we detected a font from the logo, use it
|
||||||
if detected_font_path and os.path.exists(detected_font_path):
|
if detected_font_path and os.path.exists(detected_font_path):
|
||||||
return ImageFont.truetype(detected_font_path, font_size)
|
return ImageFont.truetype(detected_font_path, font_size)
|
||||||
|
|
||||||
# Otherwise try common fonts (prioritize bold/heavy fonts)
|
# Otherwise use the first available font
|
||||||
font_paths = [
|
available_fonts = get_available_fonts()
|
||||||
# macOS paths
|
if available_fonts and available_fonts[0]['path'] != 'default':
|
||||||
'/System/Library/Fonts/Supplemental/Arial Black.ttf',
|
return ImageFont.truetype(available_fonts[0]['path'], font_size)
|
||||||
'/System/Library/Fonts/Supplemental/Impact.ttf',
|
|
||||||
'/System/Library/Fonts/Supplemental/Arial Bold.ttf',
|
|
||||||
'/System/Library/Fonts/Helvetica.ttc',
|
|
||||||
# Linux paths (DejaVu)
|
|
||||||
'/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf',
|
|
||||||
'/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf',
|
|
||||||
# Linux paths (Liberation - free alternative to Arial)
|
|
||||||
'/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf',
|
|
||||||
'/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf',
|
|
||||||
# Windows paths
|
|
||||||
'C:\\Windows\\Fonts\\ariblk.ttf',
|
|
||||||
'C:\\Windows\\Fonts\\impact.ttf',
|
|
||||||
'C:\\Windows\\Fonts\\arialbd.ttf',
|
|
||||||
]
|
|
||||||
|
|
||||||
for font_path in font_paths:
|
|
||||||
if os.path.exists(font_path):
|
|
||||||
return ImageFont.truetype(font_path, font_size)
|
|
||||||
|
|
||||||
|
# Fall back to PIL's default font
|
||||||
|
print("Warning: No TrueType fonts found, using PIL default font")
|
||||||
return ImageFont.load_default()
|
return ImageFont.load_default()
|
||||||
except:
|
except Exception as e:
|
||||||
|
print(f"Error loading font: {e}. Using default font.")
|
||||||
return ImageFont.load_default()
|
return ImageFont.load_default()
|
||||||
|
|
||||||
|
|
||||||
def add_text_to_image(image_path, text, position='below', font_size=None,
|
def add_text_to_image(image_path, text, position='below', font_size=None,
|
||||||
text_color='white', bg_color=None, padding=None):
|
text_color='white', bg_color=None, padding=None, font_path=None):
|
||||||
"""
|
"""
|
||||||
Add text to an image by expanding the canvas
|
Add text to an image by expanding the canvas
|
||||||
|
|
||||||
@@ -149,6 +261,7 @@ def add_text_to_image(image_path, text, position='below', font_size=None,
|
|||||||
text_color: Color of the text
|
text_color: Color of the text
|
||||||
bg_color: Background color for the expanded area (transparent if None and image has alpha)
|
bg_color: Background color for the expanded area (transparent if None and image has alpha)
|
||||||
padding: Padding around the text (auto if None)
|
padding: Padding around the text (auto if None)
|
||||||
|
font_path: Specific font path to use (auto if None)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
PIL Image object with text added
|
PIL Image object with text added
|
||||||
@@ -192,7 +305,7 @@ def add_text_to_image(image_path, text, position='below', font_size=None,
|
|||||||
detected_font_path = detect_font_from_image(img)
|
detected_font_path = detect_font_from_image(img)
|
||||||
|
|
||||||
# Get the appropriate font
|
# Get the appropriate font
|
||||||
font = get_font(font_size, detected_font_path)
|
font = get_font(font_size, font_path, detected_font_path)
|
||||||
|
|
||||||
# Create a temporary image to measure text size
|
# Create a temporary image to measure text size
|
||||||
temp_img = Image.new('RGB', (1, 1))
|
temp_img = Image.new('RGB', (1, 1))
|
||||||
@@ -260,13 +373,135 @@ def add_text_to_image(image_path, text, position='below', font_size=None,
|
|||||||
draw = ImageDraw.Draw(new_img)
|
draw = ImageDraw.Draw(new_img)
|
||||||
draw.text((text_x, text_y), text, fill=text_color, font=font)
|
draw.text((text_x, text_y), text, fill=text_color, font=font)
|
||||||
|
|
||||||
return new_img
|
# Prepare metadata about what was used
|
||||||
|
metadata = {
|
||||||
|
'font_size_used': font_size,
|
||||||
|
'padding_used': padding,
|
||||||
|
'font_name_used': None
|
||||||
|
}
|
||||||
|
|
||||||
|
# Try to get font name
|
||||||
|
if font_path and font_path != 'auto' and os.path.exists(font_path):
|
||||||
|
metadata['font_name_used'] = os.path.basename(font_path)
|
||||||
|
elif detected_font_path and os.path.exists(detected_font_path):
|
||||||
|
metadata['font_name_used'] = os.path.basename(detected_font_path)
|
||||||
|
else:
|
||||||
|
available_fonts = get_available_fonts()
|
||||||
|
if available_fonts and available_fonts[0]['path'] != 'default':
|
||||||
|
metadata['font_name_used'] = available_fonts[0]['name']
|
||||||
|
|
||||||
|
return new_img, metadata
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def index():
|
def index():
|
||||||
"""Serve the web interface"""
|
"""Serve the web interface"""
|
||||||
return render_template('index.html')
|
return render_template('index.html')
|
||||||
|
|
||||||
|
@app.route('/api/fonts', methods=['GET'])
|
||||||
|
def get_fonts():
|
||||||
|
"""Get list of available fonts"""
|
||||||
|
fonts = get_available_fonts()
|
||||||
|
return jsonify({'fonts': fonts})
|
||||||
|
|
||||||
|
@app.route('/api/tv-logos', methods=['GET'])
|
||||||
|
def get_tv_logos():
|
||||||
|
"""
|
||||||
|
Fetch TV logos from github.com/tv-logo/tv-logos repository
|
||||||
|
|
||||||
|
Query parameters:
|
||||||
|
- search: Search query (fuzzy, case-insensitive)
|
||||||
|
- country: Filter by country code
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Get query parameters
|
||||||
|
search_query = request.args.get('search', '').lower()
|
||||||
|
country_filter = request.args.get('country', '').lower()
|
||||||
|
|
||||||
|
# Use GitHub API to get the repository tree recursively
|
||||||
|
# Try 'main' branch (modern default)
|
||||||
|
github_api_url = "https://api.github.com/repos/tv-logo/tv-logos/git/trees/main?recursive=1"
|
||||||
|
|
||||||
|
headers = {'Accept': 'application/vnd.github.v3+json'}
|
||||||
|
response = requests.get(github_api_url, headers=headers, timeout=30)
|
||||||
|
|
||||||
|
# If 'main' doesn't exist, try 'master'
|
||||||
|
if response.status_code == 404:
|
||||||
|
github_api_url = "https://api.github.com/repos/tv-logo/tv-logos/git/trees/master?recursive=1"
|
||||||
|
response = requests.get(github_api_url, headers=headers, timeout=30)
|
||||||
|
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
tree_data = response.json()
|
||||||
|
|
||||||
|
# Determine which branch worked
|
||||||
|
branch = 'main' if 'main' in github_api_url else 'master'
|
||||||
|
|
||||||
|
logos = []
|
||||||
|
countries = set()
|
||||||
|
|
||||||
|
# Process all files in the tree
|
||||||
|
for item in tree_data.get('tree', []):
|
||||||
|
path = item.get('path', '')
|
||||||
|
|
||||||
|
# Skip if not an image file
|
||||||
|
if not path.lower().endswith(('.png', '.jpg', '.jpeg', '.svg')):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Parse the path - expected format: countries/COUNTRY/logo.png
|
||||||
|
path_parts = path.split('/')
|
||||||
|
|
||||||
|
# Extract country from path like: countries/usa/logo.png
|
||||||
|
country = None
|
||||||
|
if len(path_parts) >= 3 and path_parts[0] == 'countries':
|
||||||
|
country = path_parts[1]
|
||||||
|
countries.add(country)
|
||||||
|
elif len(path_parts) >= 2:
|
||||||
|
# Fallback for other structures - use first directory
|
||||||
|
first_dir = path_parts[0]
|
||||||
|
if first_dir not in ['.github', 'docs', 'scripts', 'paypal-donate', 'README.md']:
|
||||||
|
country = first_dir
|
||||||
|
countries.add(country)
|
||||||
|
|
||||||
|
# Skip if no country identified
|
||||||
|
if not country:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Get filename
|
||||||
|
filename = path_parts[-1]
|
||||||
|
logo_name = os.path.splitext(filename)[0]
|
||||||
|
|
||||||
|
# Apply country filter
|
||||||
|
if country_filter and country.lower() != country_filter:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Apply search filter (filename only)
|
||||||
|
if search_query and search_query not in logo_name.lower():
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Construct the raw GitHub URL
|
||||||
|
raw_url = f"https://raw.githubusercontent.com/tv-logo/tv-logos/{branch}/{path}"
|
||||||
|
|
||||||
|
logos.append({
|
||||||
|
'name': logo_name,
|
||||||
|
'country': country,
|
||||||
|
'url': raw_url,
|
||||||
|
'thumbnail': raw_url,
|
||||||
|
'path': path
|
||||||
|
})
|
||||||
|
|
||||||
|
# Sort logos by name
|
||||||
|
logos.sort(key=lambda x: x['name'].lower())
|
||||||
|
|
||||||
|
# Return ALL logos without limits - frontend will handle pagination
|
||||||
|
return jsonify({
|
||||||
|
'logos': logos,
|
||||||
|
'countries': sorted(list(countries)),
|
||||||
|
'total': len(logos)
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({'error': f'Failed to fetch logos: {str(e)}'}), 500
|
||||||
|
|
||||||
@app.route('/api/process', methods=['POST'])
|
@app.route('/api/process', methods=['POST'])
|
||||||
def process_image():
|
def process_image():
|
||||||
"""
|
"""
|
||||||
@@ -329,6 +564,11 @@ def process_image():
|
|||||||
if bg_color == '' or bg_color == 'transparent' or bg_color == 'auto':
|
if bg_color == '' or bg_color == 'transparent' or bg_color == 'auto':
|
||||||
bg_color = None
|
bg_color = None
|
||||||
|
|
||||||
|
# Get font path if specified
|
||||||
|
font_path = request.form.get('font_path', None)
|
||||||
|
if font_path == '' or font_path == 'auto':
|
||||||
|
font_path = None
|
||||||
|
|
||||||
# Save uploaded file
|
# Save uploaded file
|
||||||
filename = secure_filename(file.filename)
|
filename = secure_filename(file.filename)
|
||||||
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
||||||
@@ -336,14 +576,15 @@ def process_image():
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# Process the image
|
# Process the image
|
||||||
result_img = add_text_to_image(
|
result_img, metadata = add_text_to_image(
|
||||||
filepath,
|
filepath,
|
||||||
text,
|
text,
|
||||||
position,
|
position,
|
||||||
font_size,
|
font_size,
|
||||||
text_color,
|
text_color,
|
||||||
bg_color,
|
bg_color,
|
||||||
padding
|
padding,
|
||||||
|
font_path
|
||||||
)
|
)
|
||||||
|
|
||||||
# Save to bytes buffer
|
# Save to bytes buffer
|
||||||
@@ -383,12 +624,17 @@ def process_image():
|
|||||||
'WEBP': 'image/webp'
|
'WEBP': 'image/webp'
|
||||||
}
|
}
|
||||||
|
|
||||||
return send_file(
|
response = send_file(
|
||||||
img_io,
|
img_io,
|
||||||
mimetype=mimetype_map.get(output_format, 'image/png'),
|
mimetype=mimetype_map.get(output_format, 'image/png'),
|
||||||
as_attachment=True,
|
as_attachment=True,
|
||||||
download_name=f'processed_{filename}'
|
download_name=f'processed_{filename}'
|
||||||
)
|
)
|
||||||
|
response.headers['X-Font-Size-Used'] = str(metadata['font_size_used'])
|
||||||
|
response.headers['X-Padding-Used'] = str(metadata['padding_used'])
|
||||||
|
if metadata['font_name_used']:
|
||||||
|
response.headers['X-Font-Name-Used'] = metadata['font_name_used']
|
||||||
|
return response
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Clean up on error
|
# Clean up on error
|
||||||
@@ -460,6 +706,11 @@ def process_image_url():
|
|||||||
if bg_color == '' or bg_color == 'transparent' or bg_color == 'auto':
|
if bg_color == '' or bg_color == 'transparent' or bg_color == 'auto':
|
||||||
bg_color = None
|
bg_color = None
|
||||||
|
|
||||||
|
# Get font path if specified
|
||||||
|
font_path = request.args.get('font_path', None)
|
||||||
|
if font_path == '' or font_path == 'auto':
|
||||||
|
font_path = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Download the image
|
# Download the image
|
||||||
response = requests.get(image_url, timeout=10, headers={'User-Agent': 'LogoTextAdder/1.0'})
|
response = requests.get(image_url, timeout=10, headers={'User-Agent': 'LogoTextAdder/1.0'})
|
||||||
@@ -495,14 +746,15 @@ def process_image_url():
|
|||||||
f.write(response.content)
|
f.write(response.content)
|
||||||
|
|
||||||
# Process the image
|
# Process the image
|
||||||
result_img = add_text_to_image(
|
result_img, metadata = add_text_to_image(
|
||||||
temp_filepath,
|
temp_filepath,
|
||||||
text,
|
text,
|
||||||
position,
|
position,
|
||||||
font_size,
|
font_size,
|
||||||
text_color,
|
text_color,
|
||||||
bg_color,
|
bg_color,
|
||||||
padding
|
padding,
|
||||||
|
font_path
|
||||||
)
|
)
|
||||||
|
|
||||||
# Save to bytes buffer
|
# Save to bytes buffer
|
||||||
@@ -528,12 +780,17 @@ def process_image_url():
|
|||||||
os.remove(temp_filepath)
|
os.remove(temp_filepath)
|
||||||
|
|
||||||
# Return image directly
|
# Return image directly
|
||||||
return send_file(
|
response = send_file(
|
||||||
img_io,
|
img_io,
|
||||||
mimetype=mimetype,
|
mimetype=mimetype,
|
||||||
as_attachment=False,
|
as_attachment=False,
|
||||||
download_name=f'logo_{text[:20].replace(" ", "_")}.{output_format.lower()}'
|
download_name=f'logo_{text[:20].replace(" ", "_")}.{output_format.lower()}'
|
||||||
)
|
)
|
||||||
|
response.headers['X-Font-Size-Used'] = str(metadata['font_size_used'])
|
||||||
|
response.headers['X-Padding-Used'] = str(metadata['padding_used'])
|
||||||
|
if metadata['font_name_used']:
|
||||||
|
response.headers['X-Font-Name-Used'] = metadata['font_name_used']
|
||||||
|
return response
|
||||||
|
|
||||||
except requests.RequestException as e:
|
except requests.RequestException as e:
|
||||||
return jsonify({'error': f'Failed to download image: {str(e)}'}), 400
|
return jsonify({'error': f'Failed to download image: {str(e)}'}), 400
|
||||||
|
|||||||
1421
templates/index.html
1421
templates/index.html
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user