Banner of Automated Arabic Verse Display and Audio Integration

Render Quranic Verses as PNG Images and Play Audio Using Command Line Tools


Category: Python

Date: 15 hours ago
Views: 130


Render Quranic Verses as PNG Images and Play Audio Using Command Line Tools

In this tutorial, we'll combine a Python script (ara-to-png.py) and a shell script (artopng) to create a powerful command-line tool for rendering Quranic verses as PNG images and playing their audio. We'll also integrate previously created tools for retrieving Quranic text and playing audio from the command line.

Prerequisites

Python Environment: Ensure you have a Python virtual environment set up in ~/bin/env.
Required Dependencies: Install Pillow and a Quranic font (e.g., arfonts-arabic-typesetting.ttf) in your system.
Previous Tools: Download and set up the scripts from the following articles:

Quran Search and Display Script: Enhancing Quranic Study.

Quranic Audio Playback Script: Playing Quranic Ayat.

Step 1: Python Script (ara-to-png.py)

This script renders Arabic text into a PNG image using the specified font and supports optional highlighting of a specific line. Below is the script:

    
import sys
import textwrap
from PIL import Image, ImageDraw, ImageFont

def render_arabic_text_to_image(
    text,
    font_path,
    output_path,
    image_width=970,
    wrap_width=130,
    font_size=48,
    bg_color="#F0FFF0",
    text_color="black",
    highlight_color="#FF0000",
    highlight_line=None,
    vertical_padding=20
):
    """
    Renders an Arabic text string into an image using a specific font, with text wrapping, dynamic height,
    and optional highlighting of a specific original line.

    Parameters:
    - text: str, the Arabic paragraph to render.
    - font_path: str, path to the font file (.ttf) to use.
    - output_path: str, path where the output image will be saved.
    - image_width: int, width of the image.
    - font_size: int, size of the font.
    - bg_color: str, background color of the image.
    - text_color: str, color of the text.
    - highlight_color: str, the color to use for highlighting.
    - highlight_line: int, the 1-based index of the line to highlight (None for no highlight).
    - vertical_padding: int, padding above and below the text.
    """
    try:
        # Load the font
        font = ImageFont.truetype(font_path, font_size)
    except Exception as e:
        print(f"Error loading font: {e}")
        return

    # Split the text into original lines
    original_lines = text.split("\n")

    # Add a bullet to the start of each original line
    lines_with_bullets = [f"•{line}" for line in original_lines]

    # Initialize lists to store wrapped lines and their original line mapping
    wrapped_lines = []
    line_mapping = []  # Track which wrapped lines belong to which original line
    highlighted_lines = []  # Store indices of wrapped lines that belong to the highlighted original line

    # Loop through original lines to wrap them
    for i, line in enumerate(lines_with_bullets):
        is_highlighted = highlight_line == (i + 1)  # Check if the current original line is to be highlighted
        wrapped = textwrap.wrap(line, width=wrap_width)  # Adjust width for wrapping

        # Store the wrapped lines and highlight them if necessary
        wrapped_lines.extend(wrapped)
        line_mapping.extend([i] * len(wrapped))  # All wrapped lines belong to the same original line
        if is_highlighted:
            highlighted_lines.extend([True] * len(wrapped))  # Mark all wrapped lines of this original line as highlighted
        else:
            highlighted_lines.extend([False] * len(wrapped))  # Non-highlighted lines

    # Calculate line height and total text height
    draw = ImageDraw.Draw(Image.new("RGB", (image_width, 100), bg_color))  # Temporary image to calculate text size
    _, _, _, text_height = draw.textbbox((0, 0), "A", font=font)  # Get height of a single character
    line_height = text_height + 24  # Add spacing between lines
    total_text_height = line_height * len(wrapped_lines) + 2 * vertical_padding

    # Create the image with dynamic height
    image = Image.new("RGB", (image_width, total_text_height), bg_color)
    draw = ImageDraw.Draw(image)

    # Starting Y position for text
    y = vertical_padding

    # Draw each wrapped line, right-aligned, with optional highlighting
    for i, line in enumerate(wrapped_lines):
        text_bbox = draw.textbbox((0, 0), line, font=font)
        text_width = text_bbox[2] - text_bbox[0]

        # Align text to the right with padding
        x = image_width - text_width - 20  # Align to the right with 20px padding

        # Apply highlighting to the specified line
        if highlighted_lines[i]:
            draw.text((x, y), line, font=font, fill=highlight_color, direction="rtl")
        else:
            draw.text((x, y), line, font=font, fill=text_color, direction="rtl")

        y += line_height  # Move to the next line

    # Save the image
    image.save(output_path)
    print(f"Image saved to {output_path}")



if __name__ == "__main__":
    if len(sys.argv) < 4:
        print("Usage: python render_arabic.py '<text>' <font_path> <output_image_path>")
        sys.exit(1)

    arabic_text = sys.argv[1]
    font_path = sys.argv[2]
    output_image_path = sys.argv[3]
    if len(sys.argv) > 4:
        highlight_line=int(sys.argv[4])
    else:
        highlight_line=None

    render_arabic_text_to_image(
        text=arabic_text,
        font_path=font_path,
        output_path=output_image_path,
        highlight_line=highlight_line
    )

    

Step 2: Shell Script (artopng)

The shell script coordinates the workflow: it retrieves the Quranic text, processes it, renders it into an image, and finally plays the corresponding audio. Here is the script:

    
#!/bin/bash

ENV_PATH="$HOME/bin/env"
SCRIPT_PATH="$HOME/bin/python/ara-to-png.py"
FONT_PATH="$HOME/.fonts/arfonts-arabic-typesetting.ttf"
OUT_PNG="/tmp/out-$(date '+%s').png"
PLAYER="$HOME/bin/play-ayat"

# Ensure the environment exists
if [ ! -d "$ENV_PATH" ]; then
    echo "Error: Python environment not found at $ENV_PATH"
    echo "Please create the environment and try again."
    exit 1
fi

# Ensure the script exists
if [ ! -f "$SCRIPT_PATH" ]; then
    echo "Error: Python script not found at $SCRIPT_PATH"
    exit 1
fi

CHAPTER=$1
FIRST=$2
LAST=$3
LCOLOR=$4
SPEED=$5

if [[ -z $CHAPTER  ]]
then
	echo "usage: $0 <CHAPTER_number> <FIRST_ayah> <LAST_ayah> [line_to_highlight]"
	exit
fi
if [[ -z $FIRST  ]]
then
	echo "usage: $0 <CHAPTER_number> <FIRST_ayah> [LAST_ayah] [line_to_highlight]"
	exit
fi

# Source the environment
source "$ENV_PATH/bin/activate"
"$HOME/bin/quran-search-dir/quran-search.sh" $CHAPTER $FIRST $LAST > /dev/null
# this script gets the ayat text and saves it in /tmp/artext
# Read the text from the file
artext=$(cat /tmp/artext)

# Process each line separately
artext=$(echo "$artext" | while IFS= read -r line; do
    # Extract the part inside parentheses
    inside_parentheses=$(echo "$line" | grep -oP '\(.*?\)' | tr -d '()')

    # Remove the part inside parentheses from the current line
    line_cleaned=$(echo "$line" | sed -r 's/\(.*?\)//g')

    # Append the extracted part at the end of the line, if it exists
    if [[ -n "$inside_parentheses" ]]; then
	number=$(echo "$inside_parentheses" | tr -cd '0-9')
	arabic_text=$(echo "$inside_parentheses" | grep -oP '[^\d]+$' | sed 's/^ //')
        echo "$line_cleaned  [$arabic_text $number]"
    else
        echo "$line_cleaned"
    fi
done)

python "$SCRIPT_PATH" "$artext" "$FONT_PATH" "$OUT_PNG" $LCOLOR > /dev/null

killall feh 2>/dev/null

feh  --no-xinerama $OUT_PNG & disown

# Deactivate the environment
deactivate
killall mpv 2>/dev/null
killall mpv 2>/dev/null
$PLAYER $CHAPTER $FIRST $LAST $SPEED



    

How It Works

  1. The shell script takes the chapter number, first and last verse numbers, and an optional highlight line as arguments.

  2. It retrieves the Quranic text using the search script and processes it for rendering.

  3. The Python script renders the Arabic text into a PNG image.

  4. The rendered image is displayed using feh, and the verses' audio is played using the playback script.

Usage

    
artopng <CHAPTER_number> <FIRST_ayah> <LAST_ayah> [line_to_highlight] [play_speed]
    

Example:

    
artopng 2 255 257 2
    

This command renders verses 255-257 of Surah Al-Baqarah, highlights the second line, and plays the audio.

I am using i3 window manager, and made it so that feh has no borders and no window decoration and top bar, making it look this beautiful:

The verses 255-257 of Surah Al-Baqarah
verses 255-257 of Surah Al-Baqarah

Play sourah

    
#!/bin/bash
if [[ -z $1 ]] ; then
	echo need surah number
	exit
fi
surah_number=$1
start_ayah=${2:-1}
end_ayah=${3:-300}

for ((i=start_ayah; i <= end_ayah; i++)); do
	$HOME/bin/artopng $surah_number $i
done


    

The `play-surah` script is a handy tool designed to streamline the display and audio playback of Quranic verses. This script accepts a Surah number as a required argument and optionally allows specifying a range of Ayahs to display and play. By default, it starts from the first Ayah and processes up to the last ayah of the surah (chapter) unless specified otherwise.

Here's how the script works: for each Ayah in the specified range, it utilizes the `artopng` script to generate an image of the Ayah's text and then plays the corresponding audio. When combined with a minimalistic i3 window manager setup, which employs borderless `feh` with no decorations, this approach creates an immersive and distraction-free Quranic experience, verse by verse.

Conclusion

This tool provides an immersive Quranic study experience by combining visual and auditory elements directly from the command line. Let me know if you need help customizing or extending the scripts!



130 views


Previous Article

0 Comments, latest

No comments yet. Be the first to Comment.