Spaces:
Sleeping
Sleeping
| # -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
| # Project :- StyleXfer – "Adaptive Neural Style Transfer" | |
| # StyleXfer is an AI‑powered project built on Adaptive Instance Normalization (AdaIN) that seamlessly transfers artistic styles onto images. | |
| # By intelligently aligning feature statistics between content and style, it produces visually striking transformations while preserving the essence of the original image. | |
| # Designed to be lightweight, efficient, and creative, StyleXfer showcases the fusion of deep learning and digital artistry. | |
| #---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
| # Here in this file, we will write the code of our frontend of our Webapp actually by using the Flask | |
| # This file is essentially a Flask web application that provides a user interface for running our Neural Style Transfer (NST) model using AdaIN. | |
| import sys | |
| from pathlib import Path | |
| # Add parent directory to Python path so imports work from any location | |
| sys.path.insert(0, str(Path(__file__).parent.parent)) | |
| from huggingface_hub import hf_hub_download | |
| # import os → lets you interact with the operating system, including reading environment variables. | |
| import os | |
| import torch | |
| from dotenv import load_dotenv | |
| # Load environment variables from .env file | |
| load_dotenv(os.path.join(os.path.dirname(__file__), '..', '.env')) | |
| # Here we are importing several useful components from Flask, the Python web framework | |
| # Flask :- The main class used to create your web application. | |
| # e.g app = Flask(__name__) -> This initializes our app. | |
| # render_template :- Renders HTML files stored in the templates/ folder. | |
| # Lets you pass variables into your HTML using Jinja2 templating. | |
| # request :- Handles incoming HTTP requests (form data, query parameters, JSON, etc.).request is a module or object which is used to access the request of the client | |
| # redirect :- Sends the user to a different route after an action. | |
| # url_for :- Dynamically generates URLs for routes or static files. Safer than hardcoding paths. | |
| # url_for generates the correct URL for a given function or static file. Instead of hardcoding paths like /static/style.css or /home, you ask Flask to build them dynamically. This makes your app more portable and avoids broken links when you change routes or deploy under a subpath. | |
| # url_for(endpoint, **values) | |
| # - endpoint → usually the name of the view function (or "static" for static files). | |
| # - values → extra arguments like filenames or route parameters. | |
| # send_from_directory :- Serves files directly from a folder (like images, downloads, or generated outputs). | |
| # e.g return send_from_directory("uploads", filename) | |
| from flask import Flask, render_template, request, redirect, url_for, send_from_directory, send_file | |
| # Here we are importing FlaskForm from the flask_wtf package, which is an extension that integrates WTForms with Flask. | |
| # FlaskForm :- It’s a base class used to create web forms in Flask applications. | |
| from flask_wtf import FlaskForm | |
| # flask_bootstrap :- It’s a Flask extension that integrates the Bootstrap front‑end framework (HTML, CSS, JS) directly into your Flask app. | |
| # Instead of manually downloading Bootstrap files, you can use this extension to quickly style your templates with Bootstrap’s responsive design system. | |
| # Bootstrap :- The Bootstrap class initializes the extension with your Flask app. | |
| # e.g app = Flask(__name__) | |
| # Bootstrap(app) # attaches Bootstrap support to our app | |
| # Once initialized, you can use Bootstrap’s CSS and components (buttons, forms, navbars, grids) directly in your Jinja2 templates without extra setup. | |
| from flask_bootstrap import Bootstrap | |
| # secure_filename :- It sanitizes file names before saving uploaded files to your server. | |
| # Why: When users upload files via a Flask app, their filenames might contain unsafe characters (spaces, slashes, special symbols) or even malicious paths. | |
| # secure_filename ensures the filename is safe to use on your filesystem. | |
| from werkzeug.utils import secure_filename | |
| # Removes or replaces unsafe characters. Converts spaces to underscores. Strips directory paths (so someone can’t upload a file called ../../etc/passwd). Keeps only ASCII characters. | |
| # e.g filename = secure_filename("my resume (final).pdf") | |
| # print(filename) # Output: my_resume_final.pdf | |
| # Here we are importing several form field classes from WTForms, which is the library Flask‑WTF builds on to handle web forms | |
| # FileField :- Represents a file upload input (<input type="file">). | |
| # Used when you want users to upload images, PDFs, or other files. | |
| # SubmitField :- Represents a submit button (<input type="submit">). Triggers form submission. | |
| # FloatField :- Represents a numeric input that accepts floating‑point values. | |
| # Useful for things like percentages, weights, or blending strength (alpha in your style transfer app). | |
| # HiddenField :- Represents a hidden input (<input type="hidden">). | |
| # Stores values that shouldn’t be visible to the user but are needed when processing the form (e.g., IDs, tokens). | |
| from wtforms import FileField, SubmitField, FloatField, HiddenField | |
| # here we are importing the InputRequired validator from WTForms. | |
| # It’s a form validator used in WTForms/Flask‑WTF. Ensures that the user actually provides input for a field before the form can be submitted. | |
| # Unlike DataRequired, which checks that the data isn’t empty after type conversion, InputRequired specifically checks that the input is present in the form submission itself. | |
| from wtforms.validators import InputRequired | |
| from PIL import Image | |
| # Loads the Image class from the Pillow library (PIL = Python Imaging Library). | |
| # Provides tools to open, manipulate, and convert images. | |
| # e.g Image.open(image_path) → opens an image file. | |
| # .convert('RGB') → ensures the image is in RGB format (3 color channels), which is standard for training. | |
| # Here we are importing the image transformation utilities from PyTorch’s torchvision library. | |
| from torchvision import transforms | |
| # What torchvision Is :- | |
| # torchvision is a companion library to PyTorch, focused on computer vision tasks. | |
| # It provides: | |
| # Pretrained models (ResNet, VGG, etc.). | |
| # Common datasets (CIFAR, ImageNet, COCO). | |
| # Image utilities (loading, saving, transforming). | |
| # What transforms Does :- | |
| # transforms is a submodule inside torchvision that handles image preprocessing and augmentation. | |
| # It lets you build pipelines that convert raw images into tensors suitable for training. | |
| # Common Transforms :- | |
| # Some of the most frequently used ones include: | |
| # transforms.Resize((H, W)) → resizes image to given dimensions. | |
| # transforms.RandomCrop(size) → randomly crops image (augmentation). | |
| # transforms.CenterCrop(size) → crops from the center. | |
| # transforms.ToTensor() → converts a PIL image or NumPy array into a PyTorch tensor (scales pixel values to [0,1]). | |
| # transforms.Normalize(mean, std) → normalizes pixel values channel‑wise. | |
| # transforms.ColorJitter() → randomly changes brightness, contrast, saturation, hue. | |
| # here we are bringing in Python’s built‑in io module, which provides tools for handling streams of data (like files, text, or bytes) in memory. | |
| import io | |
| # io.StringIO :- Acts like a text file stored in memory. | |
| # io.BytesIO :- Acts like a binary file in memory. Useful for images, audio, or any binary data. | |
| # Import our existing AdaIN code | |
| # Here we are importing these VGGEncoder & Decoder classes from the models.py file of utils folder | |
| from StyleXfer_NST_code.utils.models import VGGEncoder, Decoder | |
| # Here we are importing these functions from utils.py file | |
| from StyleXfer_NST_code.utils.utils import adaptive_instance_normalization, calc_mean_std | |
| # Here this is web app i.e application which will take request & give some response | |
| app = Flask(__name__) | |
| # Flask(__name__) creates a new Flask web application object. | |
| # The __name__ variable tells Flask where to look for resources (like templates or static files). It helps Flask know the “root path” of your app. | |
| # This app object is the central piece: it handles incoming requests and sends back responses. | |
| app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'dev-key-change-in-production') | |
| app.config['ALLOWED_EXTENSIONS'] = {'png', 'jpg', 'jpeg'} | |
| # SECRET_KEY :- | |
| # Used by Flask to secure sessions and forms. | |
| # Required for features like CSRF protection in Flask‑WTF and flash messages. | |
| # Should be kept secret in production (often stored in environment variables instead of hardcoding). | |
| # UPLOAD_FOLDER :- | |
| # Defines the directory where uploaded files will be saved. | |
| # Here, it points to static/uploads, meaning uploaded images will be stored inside your project’s static/uploads folder. | |
| # You’ll typically use it with os.path.join(app.config['UPLOAD_FOLDER'], filename) when saving files. | |
| # ALLOWED_EXTENSIONS :- | |
| # A Python set listing which file types are permitted for upload. | |
| # In this case: only png, jpg, and jpeg images. | |
| # This initializes the Flask‑Bootstrap extension with your Flask application. | |
| # It automatically injects Bootstrap CSS and JS into your templates, so you can use Bootstrap’s responsive design system (buttons, forms, grids, navbars) without manually linking files. | |
| # Example effect: your Flask‑WTF forms can be rendered with Bootstrap styling using built‑in macros like bootstrap/wtf.html. | |
| Bootstrap(app) | |
| # This creates the folder defined in your app’s config (UPLOAD_FOLDER), which in your case is static/uploads. | |
| # os.makedirs creates directories recursively (so if parent folders don’t exist, they’ll be created too). | |
| # The parameter exist_ok=True prevents errors if the folder already exists — it simply does nothing in that case. | |
| # This ensures your app always has a safe place to store uploaded files before you try saving them. | |
| # app.config | |
| # It’s a dictionary‑like object that stores configuration settings for your Flask application. | |
| # You can think of it as a central place where you define key‑value pairs that control how your app behaves. | |
| # Internally, it’s just a subclass of Python’s dict, so you access values with keys like app.config['SECRET_KEY']. | |
| # It defines a Flask‑WTF form class for our style transfer app | |
| class UploadForm(FlaskForm): # Creates a custom form by inheriting from FlaskForm. Each attribute inside becomes a field in your web form. | |
| content = FileField('Content Image') | |
| style = FileField('Style Image') | |
| content_path = HiddenField() | |
| style_path = HiddenField() | |
| alpha = FloatField('Alpha', default=1.0) | |
| submit = SubmitField('Transfer Style') | |
| device = torch.device("cuda" if torch.cuda.is_available() else "cpu") | |
| # Get the directory where this app.py file is located (absolute path) | |
| app_dir = os.path.dirname(os.path.abspath(__file__)) | |
| # Keep uploads inside this Flask package even when Gunicorn starts from the repo root. | |
| app.config['UPLOAD_FOLDER'] = os.environ.get( | |
| 'UPLOAD_FOLDER', | |
| os.path.join(app_dir, 'static', 'uploads') | |
| ) | |
| os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) | |
| # Creates an instance of your VGGEncoder class. | |
| # Loads pretrained weights from the file vgg_normalised.pth (a VGG model trained on ImageNet, normalized for style transfer). | |
| # Uses absolute paths so it works regardless of where gunicorn runs from | |
| encoder = VGGEncoder(os.path.join(app_dir, 'vgg_normalised.pth')).to(device) | |
| decoder = Decoder().to(device) | |
| # Loads the trained weights for the decoder from the file decoder_final.pth. | |
| # This means your decoder has already been trained to reconstruct images, so you don't start from scratch. | |
| # After this, the encoder provides features, AdaIN adjusts them, and the decoder rebuilds the final stylized image. | |
| decoder.load_state_dict(torch.load(os.path.join(app_dir, 'experiment/final_training/decoder_final.pth'), map_location=device)) | |
| # Switches the model from training mode to evaluation mode. | |
| # This affects certain layers that behave differently during training vs. inference: | |
| # Dropout layers → disabled (no random dropping of neurons). | |
| # BatchNorm layers → use stored running statistics instead of updating them. | |
| encoder.eval() | |
| decoder.eval() | |
| # That function is a helper to validate uploaded filenames in our Flask app | |
| def allowed_file(filename): | |
| # '.' in filename :- Checks if the filename contains a dot (.). Ensures the file has an extension (e.g., image.png). | |
| # filename.rsplit('.', 1)[1].lower() :- Splits the filename into two parts: name and extension. | |
| # rsplit('.', 1) → splits from the right, only once. | |
| # "photo.png".rsplit('.', 1) → ["photo", "png"] | |
| # [1] → takes the extension part ("png"). | |
| # .lower() → makes it lowercase, so "JPG" and "jpg" are treated the same. | |
| return '.' in filename and filename.rsplit('.', 1)[1].lower() in app.config['ALLOWED_EXTENSIONS'] | |
| # This is actually the start of our style transfer function in PyTorch + torchvision. | |
| # content_image: the base image (structure to preserve). | |
| # style_image: the style image (artistic look to apply). | |
| # encoder: pretrained VGG encoder to extract features. | |
| # decoder: trained decoder to reconstruct stylized output. | |
| # alpha: blending strength between content and style. | |
| def style_transfer(content_image, style_image, encoder, decoder, alpha, device): | |
| # Creates a preprocessing pipeline for the content image using torchvision.transforms. | |
| # transforms.Resize(256) → resizes the image to 256px (reduced from 512 for memory efficiency on Render) | |
| # transforms.ToTensor() → converts the image into a PyTorch tensor (shape [C, H, W] with values in [0,1]). | |
| content_transform = transforms.Compose([ | |
| transforms.Resize(256), | |
| transforms.ToTensor() | |
| ]) | |
| style_transform = transforms.Compose([ | |
| transforms.Resize(256), | |
| transforms.ToTensor() | |
| ]) | |
| # content_transform(content_image) / style_transform(style_image) :- | |
| # Applies the preprocessing pipeline you defined earlier (Resize, ToTensor, etc.). | |
| # Converts the raw image into a PyTorch tensor with shape [C, H, W]. | |
| # .unsqueeze(0) :- Adds a new dimension at index 0. | |
| # This turns the tensor from [C, H, W] → [1, C, H, W]. | |
| # Why? Because PyTorch models expect a batch dimension, even if you’re only passing one image. | |
| # So now it’s treated as a batch of size 1. | |
| content_image = content_transform(content_image).unsqueeze(0).to(device) | |
| style_image = style_transform(style_image).unsqueeze(0).to(device) | |
| # It is the core of the style transfer process — it takes our content and style images, extracts features, blends them using AdaIN, and reconstructs the final stylized image | |
| # with torch.no_grad() :- Disables gradient tracking (no backpropagation). Saves memory and speeds up inference since you’re only generating images, not training as we already have the trained model. | |
| # The newer and better alternative to torch.no_grad() in PyTorch is torch.inference_mode(). | |
| # It was introduced to make inference faster and more memory‑efficient, and is now the recommended choice for evaluation/inference code. | |
| # If we want, we can also use this torch.inference_mode() also here. | |
| with torch.no_grad(): | |
| content_feats = encoder(content_image, is_test=True) | |
| style_feats = encoder(style_image, is_test=True) | |
| # Applies AdaIN (Adaptive Instance Normalization). Aligns the mean and variance of the content features to match those of the style features. | |
| # This is the mathematical blending of style into content. | |
| # Here we are passing content_feats & not content_feats[-1] because here this content_feats only contains the fetaure map from the last layer only as we mention is_test=True here | |
| stylized_feats = adaptive_instance_normalization(content_feats, style_feats) | |
| # Blends the AdaIN output with the original content features. | |
| # alpha controls the strength of the style transfer: | |
| # alpha = 1.0 → full style. | |
| # alpha = 0.0 → pure content. | |
| # Values in between → balanced mix. | |
| stylized_feats = alpha * stylized_feats + (1 - alpha) * content_feats | |
| # Passes the blended features into the trained decoder. Reconstructs the final stylized image in RGB space. | |
| # Output is the image you see with the artistic style applied. | |
| stylized_image = decoder(stylized_feats) | |
| return stylized_image | |
| # This function is a utility to save a PyTorch image tensor as a real image file | |
| # image: a PyTorch tensor representing the image you want to save. | |
| # path: the file path (string) where the image should be saved, e.g. "output/stylized.png". | |
| def save_image(image, path): | |
| # Moves the tensor from GPU to CPU (since PIL can’t handle CUDA tensors). | |
| # .clone() makes a copy so you don’t accidentally modify the original tensor. | |
| image = image.cpu().clone() | |
| # Removes the batch dimension. If the tensor shape is [1, C, H, W], it becomes [C, H, W]. | |
| # This is necessary because ToPILImage expects a single image, not a batch. | |
| image = image.squeeze(0) | |
| # Ensures all pixel values are between 0 and 1. | |
| # Prevents invalid values (like negatives or >1) from breaking the image conversion. | |
| image = image.clamp(0, 1) | |
| # Converts the tensor into a PIL Image object. Now it’s in a format that can be saved as .png, .jpg, etc. | |
| image = transforms.ToPILImage()(image) | |
| # Saves the PIL image to the specified file path. Example: "output/stylized.png". | |
| image.save(path) | |
| # Now we will define the main route of this app which can take both GET & POST requests | |
| # @app.route('/', methods=['GET', 'POST']) :- This is a Flask route decorator. | |
| # It maps the root URL ('/') to the index() function. | |
| # Accepts both GET (loading the page) and POST (submitting the form) requests. | |
| def index(): | |
| form = UploadForm() | |
| result_image = None | |
| content_filename = None | |
| style_filename = None | |
| # Placeholder for error messages. If something goes wrong (invalid file type, missing upload, etc.), this variable will store the error string to show in the template. | |
| error = None | |
| # Checks if the form was submitted (POST) and passed validation (all required fields are filled, CSRF token is valid, etc.). | |
| # This is a Flask‑WTF helper that combines request.method == 'POST' and form.validate(). | |
| if request.method == 'POST': | |
| # Ensures the user actually uploaded a content image file. | |
| # form.content.data → the uploaded file object. | |
| # .filename → the name of the uploaded file | |
| if form.content.data and form.content.data.filename: | |
| if allowed_file(form.content.data.filename): | |
| # Sanitizes the filename using Werkzeug’s secure_filename. | |
| # Removes unsafe characters (like spaces, slashes, special symbols) to prevent directory traversal or injection attacks. Example: "my photo.png" → "my_photo.png". | |
| content_filename = secure_filename(form.content.data.filename) | |
| # Saves the uploaded file into your configured upload folder (static/uploads). Uses os.path.join to build the full safe path. | |
| form.content.data.save(os.path.join(app.config['UPLOAD_FOLDER'], content_filename)) | |
| # Stores the filename in the hidden field content_path. | |
| # Useful for keeping track of the file path across requests. | |
| form.content_path.data = content_filename | |
| else: | |
| # If no new file was uploaded, it falls back to the previously stored filename in the hidden field. | |
| # This way, the app can reuse the last uploaded content image without requiring the user to re‑upload. | |
| content_filename = form.content_path.data | |
| if form.style.data and form.style.data.filename: | |
| if allowed_file(form.style.data.filename): | |
| style_filename = secure_filename(form.style.data.filename) | |
| form.style.data.save(os.path.join(app.config['UPLOAD_FOLDER'], style_filename)) | |
| form.style_path.data = style_filename | |
| else: | |
| style_filename = form.style_path.data | |
| # Checks that both filenames exist (i.e. the user uploaded both a content image and a style image). If either is missing, this block won’t run. | |
| if content_filename and style_filename: | |
| content_path = os.path.join(app.config['UPLOAD_FOLDER'], content_filename) | |
| style_path = os.path.join(app.config['UPLOAD_FOLDER'], style_filename) | |
| # Here we are using try except block so that errors can be caught (e.g., invalid file, processing failure). If something goes wrong, the app won’t crash — it will fall into the except block. | |
| try: | |
| content_image = Image.open(content_path).convert('RGB') | |
| style_image = Image.open(style_path).convert('RGB') | |
| alpha = float(request.form.get('alpha', form.alpha.data or 1.0)) | |
| stylized_image = style_transfer(content_image, style_image, encoder, decoder, alpha, device) | |
| result_filename = 'stylized_' + content_filename | |
| result_path = os.path.join(app.config['UPLOAD_FOLDER'], result_filename) | |
| save_image(stylized_image, result_path) | |
| result_image = result_filename | |
| except Exception as e: | |
| error = str(e) | |
| else: | |
| if not content_filename and not style_filename: | |
| error = 'Please upload both content and style images' | |
| elif not content_filename: | |
| error = 'Please upload content image' | |
| elif not style_filename: | |
| error = 'Please upload style image' | |
| # Tells Flask to render the index.html file from your templates/ folder. This is the page the user sees in their browser. | |
| return render_template('index.html', form=form, result_image=result_image, content_image=content_filename, style_image=style_filename, error=error) | |
| # It defines a Flask route for serving uploaded files back to the user | |
| # @app.route('/uploads/<filename>') :- Creates a route like /uploads/cat.png. | |
| # <filename> is a dynamic URL parameter — whatever string appears in place of <filename> gets passed into the function. | |
| # The filename argument will contain the actual file name requested in the URL (e.g., "cat.png"). | |
| def send_image(filename): | |
| # send_from_directory(app.config['UPLOAD_FOLDER'], filename) :- A Flask helper that safely serves files from a specific directory. | |
| # app.config['UPLOAD_FOLDER'] → the folder where uploads are stored (e.g., "static/uploads"). | |
| # filename → the requested file inside that folder. | |
| # Flask will locate the file and return it as an HTTP response so the browser can display or download it. | |
| return send_from_directory(app.config['UPLOAD_FOLDER'], filename) | |
| # Flask route for serving example files from a specific folder | |
| # Creates a route like /examples/demo1.png or /examples/subfolder/sample.jpg. | |
| # <path:filename> is a dynamic parameter — it captures not just a single filename but also subdirectories if present (because of path: instead of just string). | |
| # Example: /examples/styles/starry_night.jpg → filename = "styles/starry_night.jpg". | |
| def send_example(filename): | |
| # Use absolute path to examples folder, relative to this app.py file location | |
| # examples_folder = os.path.join(app_dir, 'examples') | |
| # return send_from_directory(examples_folder, filename) | |
| # Download the requested file from your dataset repo | |
| # Explicitly tell Hugging Face this is a dataset repo | |
| file_path = hf_hub_download( | |
| repo_id="Arpit16112/StyleXfer-examples", | |
| filename=filename, | |
| repo_type="dataset" # <-- critical fix | |
| ) | |
| return send_file(file_path) | |
| if __name__ == '__main__': | |
| # Imports the run_simple function from Werkzeug (the underlying library Flask uses for its development server). | |
| # run_simple is a lightweight way to start a WSGI server for development. | |
| from werkzeug.serving import run_simple | |
| # Load host and port from environment variables | |
| host = os.environ.get('FLASK_HOST', 'localhost') | |
| port = int(os.environ.get('FLASK_PORT', 5000)) | |
| # Starts the Flask app on host localhost and port 5000. | |
| # Parameters explained: | |
| # 'localhost' → binds the server to your local machine only (not accessible externally). | |
| # 5000 → the port number where the app will be available (http://localhost:5000). | |
| # app → the Flask application object you defined earlier. | |
| # use_reloader=True → automatically restarts the server when you change code files (hot reload). | |
| # use_debugger=True → enables the interactive debugger, so if an error occurs you get detailed debug info in the browser. | |
| run_simple(host, port, app, use_reloader=True, use_debugger=True) | |
| # Every Python file has a special built‑in variable called __name__. | |
| # If the file is being run directly (e.g., python train.py), then __name__ is set to "__main__". | |
| # If the file is being imported as a module into another script, then __name__ is set to the module’s name (e.g., "train"). | |
| # This check ensures the server only starts when you run the file directly, not when it’s imported elsewhere. | |
| #-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
| # SO now we can run the flask app using :- | |
| # python app.py | |
| # ----------OR-------- | |
| # flask --app app run | |
| #-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
| # Here we will write this process definition inside the Procfile.txt actually. | |
| # Here it is a process definition in a Procfile (used by Heroku and similar platforms) that tells the platform how to start your web service. | |
| # web: gunicorn --bind :$PORT app:app | |
| # web :- Defines the process type. | |
| # web is special: it means this process will handle HTTP requests. Heroku (and Render, if you use it) expects at least one web process for a web app. | |
| # gunicorn :- A production‑grade WSGI HTTP server for Python apps. It’s faster and more robust than running python app.py directly. | |
| # Commonly used to serve Flask, Django, and FastAPI apps in production. | |
| # --bind :$PORT :- Tells Gunicorn to bind to the port provided by the platform. | |
| # $PORT is an environment variable automatically set by Heroku/Render. | |
| # You don’t hardcode 5000 or 8000 because the platform dynamically assigns a port. | |
| # app:app :- Refers to your Python file and the Flask app object inside it. | |
| # First app → the filename (app.py). | |
| # Second app → the Flask application instance inside that file: | |
| # from flask import Flask | |
| # app = Flask(__name__) | |