Cookbook


Table of Contents

Network

Network Protocols and Associated Ports


HTML

Symbols: Arrows, Greek Letters, Fractions, Roman Numerals, Currency, ★

Designing a Table Layout in HTML and CSS with Adding a Separator Row

Highlighting Text with Inline Styles for Mild and Light Emphasis

Working with <pre> Tags: No Scroll, Y-Scroll, X-Scroll

Invisible Link

Dotted Horizontal Line

Aligning Text

Deprecated Features in HTML5

Creating Illustrative Graphs with Chart.js

Numbering HTML List Items from Zero

Styling Blockquotes with Inline CSS

Click-to-Copy Implementation for Preformatted Blocks

Converting a Single-Column Table into a Two-Column Structure with Functional Copy Buttons

Dotted Border Box Styling Options

Collapsible Menu with <details> and <summary>

...

Videos

Comparing Video Modals and Embedded Videos

Video Modal

Implementing an Automatic Video Modal with Adjustable Width

Modal Pop-up with Interactive Features

Embedded Video

Embedding and Optimizing HTML5 Video

How to Embed a Youtube Video and Ensure Visibility on External Website

Responsive YouTube Embedding

Responsive YouTube Video Embed

How to Circumvent YouTube Embed Issues (Written April 16, 2025)

Toggle

Reliable Methods for Storing Dev Mode Status in Web Applications

Implementing a Toggle Switch with Dev-Mode Functionality

Storing the Toggle State for Persistent Dev Mode

Resolving Asynchronous Loading Issues in Web Applications Using jQuery and Local Storage

Dynamically Updating Content Based on Dev Mode Toggle in JavaScript


Python Script

Converting HTML Tables to Excel with Python


Embedded Scripting Interface: R Packages and Python Interpreter

R Packages

Using R Packages in Python with rpy2


macOS

Installing `rpy2` in PyCharm on macOS

Windows

Integrating R's fastICA with Python using rpy2 on Windows

Troubleshooting rpy2 Script Execution in PyCharm on Windows


Adaptive Dynamic Coordinate Scaling and Visualization

Dynamic Coordinate Scaling for Mathematical Simulation

Dynamic Range Adjustment Techniques for Graph Visualization in JavaScript


Firefox Extension

Analysis of Video Stream Extraction

youtube_downloader-1.6.30

easy_youtube_video_download-19.1

Integrated Analysis of Video Stream Extraction and Downloading Mechanisms

↪ Practice: nGeneFirefoxDownloader Extension Documentation v1.0 (Written December 3rd, 2024)


Network


Network Protocols and Associated Ports

Classification Category Port Number Protocol Description
Basic 20, 21 FTP (File Transfer Protocol) Transfers files between a client and server, with Port 21 for control and Port 20 for data.
22 SSH (Secure Shell) Provides secure remote access and encrypted communication.
Web 80 HTTP (Hypertext Transfer Protocol) Transfers unencrypted web pages and resources.
443 HTTPS (Hypertext Transfer Protocol Secure) Transfers encrypted web pages and resources using SSL/TLS.
8080 HTTP Testing Commonly used as an alternative or test port for HTTP services, often for local development.
3389 RDP (Remote Desktop Protocol) Allows remote access to Windows systems.
7860 Stable Diffusion http://127.0.0.1:7860
Apple Remote Desktop (ARD) 3283 TCP/UDP Essential for Apple Remote Desktop’s core screen sharing and remote control functions.
5900 TCP Facilitates VNC (Virtual Network Computing) operations, which ARD uses to enable screen sharing.
Synology NAS: Network Ports Guide
NAS Web Access (HTTP/HTTPS) 5000 HTTP (non-secure access) Allows standard HTTP access to the Synology DSM interface.
5001 HTTPS (secure access) Provides secure HTTPS access to the Synology DSM interface, recommended for remote access.
QuickConnect http://QuickConnect.to/ngeneorg
File Services 445 SMB (Windows File Sharing) Enables file sharing over SMB protocol, commonly used for Windows file services.
548 AFP (Apple File Sharing) Allows file sharing for macOS devices over the AFP protocol.
  1. Open Finder.
  2. Press Command + K to open the "Connect to Server" dialog.
  3. In the dialog, type the following address:
    afp://ngene.org
  4. Click Connect to mount the AFP share.
20, 21 FTP (File Transfer Protocol) Transfers files between a client and server, with Port 21 for control and Port 20 for data.
990 FTPS (Secure FTP) Supports encrypted file transfer over FTP using SSL/TLS for added security.
22 SFTP (SSH File Transfer Protocol) Provides secure file transfer and remote access using SSH protocol.
WebDAV 5005 HTTP (non-secure)
5006 HTTPS (secure)
Synology Drive and Cloud Station 6690 Synology Drive Client Allows remote access and synchronization through Synology Drive.
6281–6300 Cloud Station Backup Supports Cloud Station Backup services, providing file backup and sync.
Multimedia Services 5000 or 5001 Audio Station, Video Station, and Photo Station Enables multimedia services for audio, video, and photo streaming over HTTP/HTTPS.
DSM Services (DiskStation Manager) 5000 (HTTP) / 5001 (HTTPS) DSM Web Interface Allows access to the DiskStation Manager (DSM) web interface.

https://find.synology.com to https://192.168.0.135:5001
https://ngeneorg.synology.me:5000 or https://ngeneorg.synology.me:5001


HTML


Symbols:

(A) Arrows

DescriptionSymbolHTML EntityUnicode
Right Arrow&rarr;&#8594;
Left Arrow&larr;&#8592;
Up Arrow&uarr;&#8593;
Down Arrow&darr;&#8595;
Double Right Arrow&Rightarrow;&#8658;
Long Right Arrow&Longrightarrow;&#8668;
Rightwards Dash Arrow&dashrarr;&#10550;
Right Arrow with Hook&hookrightarrow;&#8617;
North-West Arrow&nwarr;&#8598;
North-East Arrow&nearr;&#8599;
South-East Arrow&searr;&#8600;
South-West Arrow&swarr;&#8601;
Left-Right Arrow with Stroke&#8622;&#8622;
Circle Left Arrow&#8634;&#21BA;
Circle Right Arrow&#8635;&#21BB;
Right Arrow Over Left Arrow&#8644;&#21C4;
Up Down Arrow&#8645;&#21C5;
Reversible Rightward Arrow&#8652;&#21CC;
Heavy Wide-Headed Right Arrow &#x2794; &#10132;
Heavy South East Arrow &#x2798; &#10136;
Heavy Right Arrow &#x2799; &#10137;
Heavy North East Arrow &#x279A; &#10138;
Drafting Point Right Arrow &#x279B; &#10139;
Heavy Round-Tipped Right Arrow &#x279C; &#10140;
Triangle-Headed Right Arrow &#x279D; &#10141;
Heavy Triangle-Headed Right Arrow &#x279E; &#10142;
Dashed Triangle-Headed Right Arrow &#x279F; &#10143;
Heavy Dashed Triangle-Headed Right Arrow &#x27A0; &#10144;
Black Right Arrow &#x27A1; &#10145;
Three-D Top-Lighted Right Arrowhead &#x27A2; &#10146;
Three-D Bottom-Lighted Right Arrowhead &#x27A3; &#10147;
Black Right Arrowhead &#x27A4; &#10148;
Heavy Black Curved Down and Right Arrow &#x27A5; &#10149;
Heavy Black Curved Up and Right Arrow &#x27A6; &#10150;
Squat Black Right Arrow &#x27A7; &#10151;
Heavy Concave-Pointed Black Right Arrow &#x27A8; &#10152;
Right-Shaded White Right Arrow &#x27A9; &#10153;
Left-Shaded White Right Arrow &#x27AA; &#10154;
Back-Tilted Shadowed White Right Arrow &#x27AB; &#10155;
Front-Tilted Shadowed White Right Arrow &#x27AC; &#10156;
Heavy Lower Right-Shadowed White Right Arrow &#x27AD; &#10157;
Heavy Upper Right-Shadowed White Right Arrow &#x27AE; &#10158;
Notched Lower Right-Shadowed White Right Arrow &#x27AF; &#10159;
Notched Upper Right-Shadowed White Right Arrow &#x27B1; &#10161;
Circled Heavy White Right Arrow &#x27B2; &#10162;
White-Feathered Right Arrow &#x27B3; &#10163;
Black-Feathered South East Arrow &#x27B4; &#10164;
Black-Feathered Right Arrow &#x27B5; &#10165;
Black-Feathered North East Arrow &#x27B6; &#10166;
Heavy Black-Feathered South East Arrow &#x27B7; &#10167;
Heavy Black-Feathered Right Arrow &#x27B8; &#10168;
Heavy Black-Feathered North East Arrow &#x27B9; &#10169;
Teardrop-Barbed Right Arrow &#x27BA; &#10170;
Heavy Teardrop-Shanked Right Arrow &#x27BB; &#10171;
Wedge-Tailed Right Arrow &#x27BC; &#10172;
Heavy Wedge-Tailed Right Arrow &#x27BD; &#10173;
Open-Outlined Right Arrow &#x27BE; &#10174;

(B) Greek Letters

DescriptionSymbolHTML EntityUnicode
Alphaα&alpha;&#945;
Betaβ&beta;&#946;
Gammaγ&gamma;&#947;
Deltaδ&delta;&#948;
Epsilonε&epsilon;&#949;
Zetaζ&zeta;&#950;
Etaη&eta;&#951;
Thetaθ&theta;&#952;
Iotaι&iota;&#953;
Kappaκ&kappa;&#954;
Lambdaλ&lambda;&#955;
Muμ&mu;&#956;
Nuν&nu;&#957;
Xiξ&xi;&#958;
Omicronο&omicron;&#959;
Piπ&pi;&#960;
Rhoρ&rho;&#961;
Sigmaσ&sigma;&#963;
Tauτ&tau;&#964;
Upsilonυ&upsilon;&#965;
Phiφ&phi;&#966;
Chiχ&chi;&#967;
Psiψ&psi;&#968;
Omegaω&omega;&#969;

(C) Fractions

DescriptionSymbolUnicodeHTML CodeHex CodeHTML Entity
Fraction One Quarter¼&#xbc;&frac14;&#188;&#00BC;
Fraction One Half½&#xbd;&frac12;&#189;&#00BD;
Fraction Three Quarters¾&#xbe;&frac34;&#190;&#00BE;
Fraction Numerator One&#x215F;N/A&#8543;&#215F;

(D) Roman Numerals

DescriptionSymbolUnicodeHex CodeHTML Entity
Roman Numeral One&#x2160;&#8544;&#2160;
Roman Numeral Two&#x2161;&#8545;&#2161;
Roman Numeral Three&#x2162;&#8546;&#2162;
Roman Numeral Four&#x2163;&#8547;&#2163;
Roman Numeral Five&#x2164;&#8548;&#2164;
Roman Numeral Six&#x2165;&#8549;&#2165;
Roman Numeral Seven&#x2166;&#8550;&#2166;
Roman Numeral Eight&#x2167;&#8551;&#2167;
Roman Numeral Nine&#x2168;&#8552;&#2168;
Roman Numeral Ten&#x2169;&#8553;&#2169;
Roman Numeral Eleven&#x216A;&#8554;&#216A;
Roman Numeral Twelve&#x216B;&#8555;&#216B;
Small Roman Numeral One&#x2170;&#8560;&#2170;
Small Roman Numeral Two&#x2171;&#8561;&#2171;
Small Roman Numeral Three&#x2172;&#8562;&#2172;
Small Roman Numeral Four&#x2173;&#8563;&#2173;
Small Roman Numeral Five&#x2174;&#8564;&#2174;
Small Roman Numeral Six&#x2175;&#8565;&#2175;
Small Roman Numeral Seven&#x2176;&#8566;&#2176;
Small Roman Numeral Eight&#x2177;&#8567;&#2177;
Small Roman Numeral Nine&#x2178;&#8568;&#2178;
Small Roman Numeral Ten&#x2179;&#8569;&#2179;
Small Roman Numeral Eleven&#x217A;&#8570;&#217A;
Small Roman Numeral Twelve&#x217B;&#8571;&#217B;

(E) Currency Symbols

DescriptionSymbolUnicodeHex CodeHTML CodeHTML Entity
Dollar Sign$&#x24;&#36;&dollar;\0024
Cent Sign¢&#xa2;&#162;&cent;\00A2
Pound Sign£&#xa3;&#163;&pound;\00A3
Euro Sign&#x20AC;&#8364;&euro;\20AC
Yen Sign¥&#xa5;&#165;&yen;\00A5
Indian Rupee Sign&#x20B9;&#8377;N/A\20B9
Ruble Sign&#x20BD;&#8381;N/A\20BD
Yuan Character, in China&#x5143;&#20803;N/A\5143
Currency Sign¤&#xa4;&#164;&curren;\00A4
Euro-Currency Sign&#x20A0;&#8352;N/A\20A0
Colon Sign&#x20A1;&#8353;N/A\20A1
Cruzeiro Sign&#x20A2;&#8354;N/A\20A2
French Franc Sign&#x20A3;&#8355;N/A\20A3
Lira Sign&#x20A4;&#8356;N/A\20A4
Mill Sign&#x20A5;&#8357;N/A\20A5
Naira Sign&#x20A6;&#8358;N/A\20A6
Won Sign&#x20A9;&#8361;N/A\20A9
New Sheqel Sign&#x20AA;&#8362;N/A\20AA
Dong Sign&#x20AB;&#8363;N/A\20AB
Kip Sign&#x20AD;&#8365;N/A\20AD
Tugrik Sign&#x20AE;&#8366;N/A\20AE
Drachma Sign&#x20AF;&#8367;N/A\20AF
German Penny Symbol&#x20B0;&#8368;N/A\20B0
Peso Sign&#x20B1;&#8369;N/A\20B1
Guarani Sign&#x20B2;&#8370;N/A\20B2
Austral Sign&#x20B3;&#8371;N/A\20B3
Hryvnia Sign&#x20B4;&#8372;N/A\20B4
Cedi Sign&#x20B5;&#8373;N/A\20B5
Thai Baht฿&#x0E3F;&#3647;N/A\0E3F
Yen Character&#x5186;&#20870;N/A\5186
Won Character&#xC6D0;&#50896;N/A\C6D0

(F) ★ &starf;

Description Symbol HTML Entity Unicode
Copyright Sign © &copy; &#169;
Registered Sign ® &reg; &#174;
Trademark Sign &trade; &#8482;
Pilcrow Sign &para; &#182;
Section Sign § &sect; &#167;
Celsius Sign &#x2103; &#8451;
Fahrenheit Sign &#x2109; &#8457;
Script Capital R &Rscr; &#8475;
Real Part Symbol &real; &#8476;
Double-Struck R &Ropf; &#8477;
Ohm Sign Ω &#x2126; &#8486;
Mho Sign &mho; &#8487;
Angstrom Sign Å &#x212B; &#8491;
Sun Symbol &#x2600; &#9728;
Cloud &#x2601; &#9729;
Umbrella &#x2602; &#9730;
Snowman &#x2603; &#9731;
Black Star &starf; &#9733;
White Star &star; &#9734;
Female Sign &female; &#9792;
Male Sign &male; &#9794;
Pluto Sign &#x2647; &#9799;
White Chess King &#x2654; &#9812;
White Chess Queen &#x2655; &#9813;
White Chess Rook &#x2656; &#9814;
White Chess Bishop &#x2657; &#9815;
White Chess Knight &#x2658; &#9816;
White Chess Pawn &#x2659; &#9817;
Black Chess King &#x265A; &#9818;
Black Chess Queen &#x265B; &#9819;
Black Chess Rook &#x265C; &#9820;
Black Chess Bishop &#x265D; &#9821;
Black Chess Knight &#x265E; &#9822;
Black Chess Pawn &#x265F; &#9823;
Black Spade Suit &spades; &#9824;
White Heart Suit &#x2661; &#9825;
White Diamond Suit &#x2662; &#9826;



Designing a Table Layout in HTML and CSS

The following guide provides an overview of styling an HTML table using CSS for a balanced and aesthetically pleasing presentation. The table structure features subtle hover effects, clearly defined headers, and flexible column layouts, all adaptable to various content types. This refined approach ensures readability and organization within any web document.


CSS Style for Table

The CSS styling below applies essential formatting to create a simple yet polished table layout. This style includes adjustments for width, padding, border effects, and hover interactions, presenting content in a user-friendly, structured manner. Each CSS rule is carefully chosen to enhance visual clarity without overwhelming the content.

<style>
    .simple-table {
        width: 100%;
        border-collapse: collapse;
        margin: 20px 0;
        text-align: left;
    }
    .simple-table th, .simple-table td {
        padding: 3px 3px;
        border-bottom: 1px solid #ddd;
    }
    .simple-table th {
        background-color: #f2f2f2;
    }
    .simple-table tr:hover {
        background-color: #f5f5f5;
    }
    .simple-classification-header {
        background-color: #f8f8f8; /* Achromatic color for section headers */
        text-align: left;
        font-weight: bold;
    }
</style>

HTML Structure for Table

Below is the HTML table structure, where each column header and cell is generically labeled. This generic approach allows the table to adapt to a wide range of content without limiting it to specific data types.

<div style="overflow-x: auto;">
    <table class="simple-table">
        <thead>
            <tr>
                <th>First Column Header</th>
                <th>Second Column Header</th>
                <th>Third Column Header</th>
                <th>Fourth Column Header</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>First Column Content</td>
                <td>Second Column Content</td>
                <td>Third Column Content</td>
                <td>Fourth Column Content</td>
            </tr>
            <tr>
                <td>First Column Content</td>
                <td>Second Column Content</td>
                <td>Third Column Content</td>
                <td>Fourth Column Content</td>
            </tr>
        </tbody>
    </table>
</div>

This structure includes a table wrapper with overflow-x: auto; to enable horizontal scrolling for responsive viewing on smaller screens.


Adding a Separator Row

Separator rows can be added by creating a table row with a single cell that spans multiple columns, using the colspan attribute. Adding a border style to this row, such as a thick top border, visually divides table sections.

Naive Separator

Separator rows can be added by creating a table row with a single cell that spans multiple columns, using the colspan attribute. Adding a border style to this row, such as a thick top border, visually divides table sections.

<tr>
    <td colspan="4" style="border-top: 2px solid #000;"></td>
</tr>

Simple Separator

Use the following code to add a simple separator row. This row spans across all columns and includes a thick top border to divide table sections.

<tr>
    <td colspan="4" style="border-top: 2px solid #000; background-color: #f0f0f0; padding: 4px; border-radius: 3px;">
    </td>
</tr>

Separator with Text

To add a separator row that includes descriptive text and a distinct background, use the following code. This enhances the visual separation by providing context or labeling for the upcoming section.

<tr>
    <td colspan="5" style="background-color: #f0f0f0; border-top: 2px solid #000; padding: 8px; text-align: center;">
        <strong> ~~~Contents~~~ </strong>
    </td>
</tr>

Creating a Table with Merged Cells

To group table data, the rowspan and colspan attributes allow merging cells across rows or columns. For instance, grouping rows by classification or category enhances clarity and reduces redundancy.

<table class="simple-table">
  <tr>
      <th>Classification</th>
      <th>Category</th>
      <th>Port Number</th>
      <th>Protocol</th>
      <th>Description</th>
  </tr>

  <!-- Basic Classification Rows -->
  <tr>
      <td rowspan="7">Basic</td>
      <td rowspan="2"></td>
      <td>20, 21</td>
      <td>FTP (File Transfer Protocol)</td>
      <td>Transfers files between a client and server.</td>
  </tr>
  <tr>
      <td>22</td>
      <td>SSH (Secure Shell)</td>
      <td>Provides secure remote access.</td>
  </tr>
  <!-- More rows here -->
</table>




Highlighting Text with Inline Styles for Mild and Light Emphasis


Working with <pre> Tags: No Scroll, Y-Scroll, X-Scroll

  1. Non-Scrollable <pre> Block

    This version grows vertically with the content and allows text to wrap naturally. It does not restrict either width or height, so very long text may become unwieldy on the page.

    <pre style="white-space: pre-wrap;">
        Your content will wrap within the width of the block without creating a horizontal scroll bar. 
        However, if the content is too long vertically, the block will grow naturally, and no scroll will appear.
    </pre>
    • white-space: pre-wrap;
      • Preserves whitespace and line breaks like the default <pre>.
      • Allows text to wrap within the width of the container, preventing horizontal scrolling.
    • No max-height or overflow properties
      • The <pre> block expands naturally in the vertical direction.
      • Useful when you want the entire content to be visible without scroll bars.
  2. Scrollable <pre> Block (Vertical Scrolling)

    Limiting the height and adding vertical scrolling can help maintain a compact layout. Content exceeding the defined max-height will be scrollable within the <pre> area.

    <pre style="white-space: pre-wrap; max-height: 200px; overflow-y: auto;">
    Your code or text goes here. The block will scroll vertically if it exceeds 200px in height.
    </pre>
    • white-space: pre-wrap;
      • Ensures long lines of text wrap to the next line, preventing a wide horizontal overflow.
    • max-height: 200px;
      • Limits the maximum height to 200px.
      • Creates a vertical scroll bar if content exceeds this height.
    • overflow-y: auto;
      • Adds a vertical scroll bar once content extends beyond the maximum height.
  3. Scrollable <pre> Block (Horizontal Scrolling)

    Useful for code or text with very long lines. Setting overflow-x: auto; ensures a horizontal scroll bar appears rather than forcing the lines to wrap or stretching the page horizontally.

    <pre style="white-space: pre; overflow-x: auto;">
    Your long code or text goes here. If the line is too wide, a horizontal scroll bar will appear.
    This allows you to maintain the original formatting and indentation without wrapping.
    </pre>
    • white-space: pre;
      • Preserves all whitespace, tabs, and line breaks exactly as typed.
      • Keeps text on one line rather than wrapping.
    • overflow-x: auto;
      • Adds a horizontal scroll bar if text exceeds the width of the parent container.
      • Ensures the code or text formatting remains intact without forced wrapping.

Written on April 16, 2025


Invisible Link

In some cases, it may be useful to have a hyperlink that is visually hidden but still functional. This can be achieved using inline CSS to set the link’s color to match the background, remove the underline, and define the cursor behavior. The following code demonstrates how to make a link invisible.

<a href="./rough_drafts.html" style="color: rgba(211, 239, 224, 0); text-decoration: none; cursor: pointer;">&</a>



Dotted Horizontal Line

In some designs, a dotted horizontal line can be useful to subtly divide sections without a strong visual impact. This effect can be achieved by customizing the <hr> element's border properties using inline CSS. The following code demonstrates how to create a dotted horizontal line.

<hr style="border: 0; border-top: 1px dotted grey; margin-left: 10px; margin-right: 10px;">

This code creates a subtle divider that can be used to delineate content sections while maintaining a minimalist appearance:





Aligning Text

Feature align="center" style="text-align: center;"
Support Deprecated in HTML5 Fully supported in modern CSS
Readability Quick and easy for small projects Clear separation of content/style
Maintainability Limited for complex designs Highly maintainable
Best Practices Not recommended Strongly recommended

(1) Using the align Attribute

The align attribute, though now deprecated in HTML5, is a simple way to align text within an HTML element. It can be set to values such as center, left, or right.

<p align="center">This paragraph is centered using the align attribute.</p>

(2) Using CSS text-align Property

The text-align CSS property provides a modern and flexible way to align text. It allows you to center, left-align, or right-align text with precision.

<p style="text-align: center;">This paragraph is centered using the text-align CSS property.</p>

CSS Styling Example for Reusability

You can define a reusable CSS class for centering text:

<style>
    .center-text {
        text-align: center;
    }
</style>
<p class="center-text">This paragraph uses a CSS class to center text.</p>



Deprecated Features in HTML5

In the evolution of web development, HTML5 has emerged as a standard that emphasizes clean, semantic markup and the separation of content, presentation, and behavior. The term "deprecated" in HTML5 refers to features—such as elements or attributes—that are no longer recommended for use. While these deprecated features may still function in some browsers for backward compatibility, they are marked for potential removal in future versions and are not guaranteed to work indefinitely. This guide explores what "deprecated" means in the context of HTML5, reasons behind deprecations, examples of deprecated features along with their recommended alternatives, and provides a summary table for quick reference.

Deprecated Feature Deprecated Usage Recommended Alternative
<font> Element <font face="Arial" size="4" color="blue"> Use CSS: <span style="font-family: Arial;">
<center> Element <center> Use CSS: <div style="text-align: center;">
align Attribute <img align="right"> Use CSS: <img style="float: right;">
bgcolor Attribute <table bgcolor="lightblue"> Use CSS: <table style="background-color: lightblue;">
border Attribute <table border="1"> Use CSS: <table style="border: 1px solid black;">
<applet> Element <applet> Use <iframe> or modern technologies
<basefont> Element <basefont> Use CSS styles or external stylesheets
<frame> and <frameset> <frameset> Use <iframe> and CSS for layout
name Attribute for Anchors <a name="bookmark"> Use id attribute: <a id="bookmark">
<isindex> Element <isindex> Use <form> with input elements

Below is a detailed list of commonly deprecated features in HTML5, along with their recommended alternatives:

1. <font> Element

Deprecated Usage:

<font face="Arial" size="4" color="blue">Sample Text</font>

The <font> element was traditionally used to define font face, size, and color directly within HTML. This approach intertwines content with presentation, making the code less maintainable.

Recommended Alternative:

<span style="font-family: Arial; font-size: 16px; color: blue;">Sample Text</span>

Using CSS within a <span> element or an external stylesheet separates styling from content, allowing for greater consistency and easier updates.

2. <center> Element

Deprecated Usage:

<center>This text is centered.</center>

The <center> element centers content horizontally but mixes presentation with structure.

Recommended Alternative:

<div style="text-align: center;">This text is centered.</div>

Alternatively, apply a CSS class or use an external stylesheet for better scalability.

3. align Attribute

Deprecated Usage:

<img src="image.jpg" align="right">

The align attribute was used on various elements to control alignment, but it lacks the flexibility of CSS.

Recommended Alternative:

<img src="image.jpg" style="float: right;">

CSS properties like float, text-align, and vertical-align offer more precise control over element positioning.

4. bgcolor Attribute

Deprecated Usage:

<table bgcolor="lightblue">
    <tr>
        <td>Sample Text</td>
    </tr>
</table>

The bgcolor attribute sets background colors but is limited in styling capabilities.

Recommended Alternative:

<table style="background-color: lightblue;">
    <tr>
        <td>Sample Text</td>
    </tr>
</table>

CSS provides a wide range of background styling options, including gradients and images.

5. border Attribute for Tables

Deprecated Usage:

<table border="1">
    <tr>
        <td>Sample Text</td>
    </tr>
</table>

The border attribute offers minimal control over border styling.

Recommended Alternative:

<table style="border: 1px solid black;">
    <tr>
        <td>Sample Text</td>
    </tr>
</table>

CSS allows detailed customization of borders, including style, width, and color.

6. <applet> Element

Deprecated Usage:

<applet code="SampleApplet.class" width="300" height="300"></applet>

The <applet> element was used to embed Java applets, which are now largely unsupported due to security concerns.

Recommended Alternative:

<iframe src="path_to_content" width="300" height="300"></iframe>

The <iframe> element or modern technologies like HTML5 <canvas> and WebGL are preferred for embedding interactive content.

7. <basefont> Element

Deprecated Usage:

<basefont face="Verdana" size="3" color="green">

The <basefont> element sets default font properties for a document but lacks specificity and control.

Recommended Alternative:

<style>
    body {
        font-family: Verdana;
        font-size: 14px;
        color: green;
    }
</style>

Using CSS to define global typography ensures consistency and ease of maintenance.

8. <frame> and <frameset> Elements

Deprecated Usage:

<frameset cols="50%,50%">
    <frame src="frame1.html">
    <frame src="frame2.html">
</frameset>

Frames divide a browser window into multiple sections but can cause usability and accessibility issues.

Recommended Alternative:

<iframe src="frame1.html" style="width: 50%; height: 100%; float: left;"></iframe>
<iframe src="frame2.html" style="width: 50%; height: 100%; float: right;"></iframe>

Using <iframe> elements within a responsive layout provides better control and integration with modern web applications.

9. name Attribute for Anchors

Deprecated Usage:

<a name="bookmark">Bookmark</a>

The name attribute for anchors is outdated and conflicts with the use of id attributes.

Recommended Alternative:

<a id="bookmark">Bookmark</a>

The id attribute uniquely identifies elements and is compatible with CSS and JavaScript.

10. <isindex> Element

Deprecated Usage:

<isindex prompt="Search:">

The <isindex> element provides a single-line text input but is limited in functionality.

Recommended Alternative:

<form action="search.php" method="get">
    <label for="search">Search:</label>
    <input type="text" id="search" name="q">
    <button type="submit">Go</button>
</form>

Modern form elements offer extensive capabilities for user input and validation.



Creating Illustrative Graphs with Chart.js

Creating interactive and visually appealing graphs on a webpage is streamlined through the utilization of HTML in conjunction with JavaScript libraries such as Chart.js. This guide elucidates the general procedures required to construct various types of charts, facilitating future reference for similar tasks.

Chart.js is a widely recognized open-source JavaScript library designed to facilitate the creation of aesthetically pleasing and interactive charts and graphs. Its versatility encompasses a multitude of chart types, coupled with extensive customization options to suit diverse visualization needs.


Setting Up Your HTML File

The foundational step involves establishing a basic HTML structure. The following template serves as a starting point:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Your Chart Title</title>
</head>
<body>
    <!-- Content will be added here -->
</body>
</html>

Including the Chart.js Library

Incorporating the Chart.js library is essential for rendering charts. This can be achieved by embedding a <script> tag that references the library via a Content Delivery Network (CDN):

<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>

This script tag should be placed either within the <head> section or immediately before the closing </body> tag.

Creating a Canvas Element

The <canvas> element serves as the rendering surface for the charts. Assigning a unique id to each canvas allows for precise targeting within JavaScript:

<canvas id="myChart" width="800" height="400"></canvas>

The width and height attributes can be adjusted to accommodate the desired chart dimensions.


Drawing Different Types of Charts

  1. Line Chart

    Example: Plotting an exchange rate over time.

    HTML:

    <canvas id="lineChart" width="800" height="400"></canvas>

    JavaScript:

    <script>
    const ctx = document.getElementById('lineChart').getContext('2d');
    const lineChart = new Chart(ctx, {
        type: 'line',
        data: {
            labels: ['Date1', 'Date2', 'Date3', '...'],
            datasets: [{
                label: 'Dataset Label',
                data: [value1, value2, value3, ...],
                backgroundColor: 'rgba(54, 162, 235, 0.2)', // Fill color under the line
                borderColor: 'rgba(54, 162, 235, 1)', // Line color
                borderWidth: 2,
                fill: true, // Whether to fill under the line
                tension: 0.1 // Smoothness of the line
            }]
        },
        options: {
            responsive: true,
            plugins: {
                title: {
                    display: true,
                    text: 'Chart Title',
                    font: {
                        size: 18
                    }
                },
                legend: {
                    display: true,
                    position: 'top'
                }
            },
            scales: {
                x: {
                    title: {
                        display: true,
                        text: 'X-axis Label'
                    }
                },
                y: {
                    title: {
                        display: true,
                        text: 'Y-axis Label'
                    },
                    beginAtZero: false
                }
            }
        }
    });
    </script>
  2. Bar Chart

    Example: Displaying estimated liquidity distribution.

    HTML:

    <canvas id="barChart" width="800" height="400"></canvas>

    JavaScript:

    <script>
    const ctx = document.getElementById('barChart').getContext('2d');
    const barChart = new Chart(ctx, {
        type: 'bar',
        data: {
            labels: ['Category1', 'Category2', '...'],
            datasets: [{
                label: 'Dataset Label',
                data: [value1, value2, ...],
                backgroundColor: ['color1', 'color2', ...]
            }]
        },
        options: {
            responsive: true,
            plugins: {
                title: {
                    display: true,
                    text: 'Chart Title'
                },
                legend: {
                    display: false
                }
            },
            scales: {
                y: {
                    beginAtZero: true,
                    title: {
                        display: true,
                        text: 'Y-axis Label'
                    }
                }
            }
        }
    });
    </script>
  3. Pie Chart

    Example: Illustrating the proportion of a whole, such as a decrease in deposits.

    HTML:

    <canvas id="pieChart" width="400" height="400"></canvas>

    JavaScript:

    <script>
    const ctx = document.getElementById('pieChart').getContext('2d');
    const pieChart = new Chart(ctx, {
        type: 'pie',
        data: {
            labels: ['Segment1', 'Segment2', '...'],
            datasets: [{
                data: [value1, value2, ...],
                backgroundColor: ['color1', 'color2', ...],
                hoverOffset: 4
            }]
        },
        options: {
            responsive: true,
            plugins: {
                title: {
                    display: true,
                    text: 'Chart Title'
                },
                legend: {
                    position: 'top'
                }
            }
        }
    });
    </script>

Numbering HTML List Items from Zero

In certain web development scenarios, it becomes beneficial to display lists starting at zero rather than the default of one. This guide presents a concise, user-friendly approach to achieve such an effect through inline styling. The method offered below operates without external style sheets or embedded <style> sections. The following points illustrate an effective implementation.

Purpose of Starting Lists at Zero

Inline Styling Example

Below is a complete snippet demonstrating an ordered list beginning at zero:

<li><a href="#code">Code and Step-by-Step Explanation</a>
    <ol style="counter-reset: list-counter -1; list-style: none; padding-left: 0;">
        <li style="counter-increment: list-counter;">
            <span style="content: counter(list-counter) '. '; display: inline-block;">0. </span>
            <a href="#install-and-load">Install and Load Required Packages</a>
        </li>
        <li style="counter-increment: list-counter;">
            <span style="content: counter(list-counter) '. '; display: inline-block;">1. </span>
            <a href="#define-output">Define Output Directory</a>
        </li>
        <!-- Additional list items follow the same pattern -->
    </ol>
</li>
  1. counter-reset
    Initializes the counter at -1, ensuring that the first increment displays as zero.
  2. list-style: none; padding-left: 0;
    Removes default list styling and aligns the text as desired.
  3. counter-increment: list-counter;
    Increments the list-counter for each list item (<li>).
  4. Prefixed Numbers in <span>
    Inserts the actual numbers (e.g., 0., 1., etc.) in a <span> element. Inline CSS cannot dynamically display the counter value in the content property, so manual insertion of the incremented value is necessary.

Written on December 24th, 2024


Styling Blockquotes with Inline CSS

Blockquotes are essential elements in HTML used to denote sections of content that are quoted from another source. Applying custom styles enhances their visual distinction and improves readability. This guide provides a structured approach to styling blockquotes using inline CSS, eliminating the need for external style sheets or embedded <style> sections.

Purpose of Customizing Blockquotes

Inline Styling Example

The following snippet demonstrates how to style a blockquote with specific inline CSS properties:

<blockquote style="border-left: 4px solid #ccc; padding-left: 16px; color: #555; font-style: italic; margin-bottom: 20px;">
    This is an example of a styled blockquote. Inline CSS is used to apply custom styles directly to the element.
</blockquote>
  1. border-left: 4px solid #ccc;
    Adds a left border with a width of 4 pixels, a solid style, and a light gray color (#ccc) to visually separate the blockquote from the surrounding content.
  2. padding-left: 16px;
    Inserts 16 pixels of padding to the left side of the blockquote, ensuring that the text does not overlap with the left border and maintains adequate spacing.
  3. color: #555;
    Sets the text color to a medium gray (#555), providing a subtle contrast against the default text color and enhancing readability.
  4. font-style: italic;
    Applies an italic style to the text within the blockquote, traditionally used to emphasize quoted material.
  5. margin-bottom: 20px;
    Adds a 20-pixel margin below the blockquote, creating space between the quote and subsequent content for better visual separation.

Written on December 5th, 2024


Click-to-Copy Implementation for Preformatted Blocks

Multiple approaches exist to implement a convenient "Click to Copy" feature for content enclosed within <pre> blocks. Two notable strategies—Version 1 and Version 3—rely on modern and traditional clipboard APIs, respectively. Both approaches incorporate a user-friendly button that copies the content directly to the clipboard with minimal effort.

Below is an integrated, refined explanation of these two methods, complete with annotated code snippets and concise notes on their operational differences. This writing maintains a formal perspective and aims to provide clarity, structure, and a professional overview.

Aspect Version 1 Version 2
Clipboard API Modern navigator.clipboard.writeText Legacy document.execCommand
Implementation Inline JS function triggered by onclick Hidden <textarea> with execCommand
Browser Support Requires relatively modern browsers Broader support, especially for older browsers
Complexity Straightforward and promise-based Involves more steps but provides fallback

Version 1: Single Function per Button (Modern Clipboard API)

This version attaches inline JavaScript to each copy button. When the button is clicked, the script accesses the adjacent <pre> element and calls navigator.clipboard.writeText(...) to copy its text. The built-in promise-based API confirms successful copying and can provide immediate visual feedback.

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Version 1: Single Function per Button</title>
      <style>
        .copy-button {
          float: right;
          margin-top: 5px;
          cursor: pointer;
          background-color: #007bff;
          color: white;
          padding: 5px 10px;
          border: none;
          border-radius: 4px;
        }
        .copy-button:hover {
          background-color: #0056b3;
        }
        pre {
          position: relative;
          background-color: #f4f4f4;
          padding: 10px;
          border-radius: 5px;
          border: 1px solid #ddd;
          white-space: pre-wrap;
          margin-bottom: 0;
        }
      </style>
  
      <script>
        function copyToClipboard(button) {
          const preTag = button.previousElementSibling;
          const textToCopy = preTag.textContent;
          navigator.clipboard.writeText(textToCopy).then(() => {
            button.textContent = 'Copied!';
            setTimeout(() => { button.textContent = 'Copy'; }, 2000);
          });
        }
      </script>
    </head>
    <body>
    
      <h1>Version 1: Single Function per Button (Inline JS)</h1>
    
      <div style="width: 50%; margin: 0 auto;">
        <pre>
    This is some sample text 
    inside a preformatted block.
        </pre>
         <button class="copy-button" onclick="copyToClipboard(this)"> Copy </button>
      </div>
    
      <br>
    
      <div style="width: 50%; margin: 0 auto;">
        <pre>
    Another pre block 
    with more lines to copy.
        </pre>
        <button class="copy-button" onclick="copyToClipboard(this)">Copy</button>
      </div>
    
    </body>
    </html>



Version 2: Using a Hidden <textarea> and execCommand('copy')

This version uses the older but still viable document.execCommand('copy'). A hidden <textarea> is created and populated with the <pre> text, allowing selection and copying of the content. The <textarea> is removed afterward. This approach can enhance compatibility with certain legacy browsers.

<!DOCTYPE html>
<html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Version 3: Using Hidden <textarea> and execCommand</title>
      <style>
        .copy-button {
          float: right;
          margin-top: 5px;
          cursor: pointer;
          background-color: #17a2b8;
          color: white;
          padding: 5px 10px;
          border: none;
          border-radius: 4px;
        }
        .copy-button:hover {
          background-color: #117a8b;
        }
        pre {
          position: relative;
          background-color: #f4f4f4;
          padding: 10px;
          border-radius: 5px;
          border: 1px solid #ddd;
          white-space: pre-wrap;
          margin-bottom: 0;
        }
      </style>  
    </head>
    <body>
    
      <h1>Version 2: Using Hidden <textarea> and execCommand</h1>
    
      <div style="width: 50%; margin: 0 auto;">
        <pre id="code-block-1">
    Sample text for Version 3,
    using hidden textarea.
        </pre>
        <button class="copy-button" onclick="copyWithExecCommand('code-block-1')"> Copy </button>
      </div>
    
      <br>
    
      <div style="width: 50%; margin: 0 auto;">
        <pre id="code-block-2">
    Another text block that 
    can be copied using execCommand.
        </pre>
        <button class="copy-button" onclick="copyWithExecCommand('code-block-2')">Copy</button>
      </div>
    
      <script>
        function copyWithExecCommand(preId) {
          const preElement = document.getElementById(preId);
          const textToCopy = preElement.textContent;
    
          // Create a hidden textarea element
          const textArea = document.createElement("textarea");
          textArea.value = textToCopy;
          textArea.style.position = "fixed";
          textArea.style.top = "-9999px";
          document.body.appendChild(textArea);
    
          // Select and copy
          textArea.select();
          try {
              document.execCommand("copy");
              // Visual feedback
              const button = preElement.nextElementSibling;
              button.textContent = "Copied!";
              setTimeout(() => { button.textContent = "Copy"; }, 2000);
          } catch (err) {
              console.error('Failed to copy: ', err);
              const button = preElement.nextElementSibling;
              button.textContent = "Error";
              setTimeout(() => { button.textContent = "Copy"; }, 2000);
          }
    
          // Cleanup
          document.body.removeChild(textArea);
        }
      </script> 
    </body>
</html>


Written on December 31th, 2024


Converting a Single-Column Table into a Two-Column Structure with Functional Copy Buttons

The following explanations summarize the modifications required to transform a one-column table layout into a two-column structure, ensuring that Copy buttons can accurately copy corresponding text from <pre> blocks. The new layout offers cleaner organization and more robust functionality, following a professional, hierarchical approach.

1. Table Structure Modifications

  1. Separating <pre> Content and Copy Buttons

    • Each table row (<tr>) has been updated to contain two cells (<td>).
    • The first cell holds the <pre> block with the text to be copied, and the second cell contains the Copy button.
  2. Ensuring Correct Alignment

    • The <pre> elements remain in the left column (<td>), while each corresponding Copy button is placed in the right column (<td>).
    • Proper vertical-align: top; ensures that the text and buttons are visually aligned.
  3. HTML Example

    Below is a simplified illustration of the two-column structure:

    Be formal, without using I we you. Use humble tone. Make title. 
    Based on these different version of writings above, additively, rewrite this into integrated refined writing. When you integrate, please do not remove thoughts and idea, but rearrange and develope further if applicable.
    I will give you flexibility to refine and upgrade the writing by rearranging thoughts and develop. 
    Double check the writing and proof read before publishing 
    I need systemic, hierarchical, comprehenstive, professional writing for publishing. 
    If you could enhance the writing with bold-face, table, chart, and other illustrative format, please do so.

2. Consolidated Copy Function

  1. Combining the Copy Logic

    • A single function named copyToClipboard_enhanced replaces any previous functions, such as copyWithExecCommand, to avoid confusion.
    • This function employs the modern Clipboard API (navigator.clipboard) and ensures that the exact <pre> content from the same row is copied.
  2. Locating the <pre> Element

    • The button uses button.closest('tr') to find its row and then tr.querySelector('pre') to locate the text targeted for copying.
  3. User Feedback

    • The button text and styling briefly change to indicate successful copying or errors.
    • After two seconds, the button reverts to its original text and color.
  4. Example Code

    <script>
        function copyToClipboard_enhanced(button) {
            const tr = button.closest('tr');
            if (!tr) {
                console.error('Copy button is not inside a table row.');
                return;
            }
    
            const pre = tr.querySelector('pre');
            if (!pre) {
                console.error('No <pre> element found in the same row as the copy button.');
                return;
            }
    
            const textToCopy = pre.textContent;
    
            navigator.clipboard.writeText(textToCopy).then(() => {
                const originalText = button.textContent;
                button.textContent = 'Copied!';
                button.style.backgroundColor = '#218838'; 
    
                setTimeout(() => {
                    button.textContent = originalText;
                    button.style.backgroundColor = '#28a745'; 
                }, 2000);
            }).catch(err => {
                console.error('Failed to copy text: ', err);
                button.textContent = 'Error';
                setTimeout(() => {
                    button.textContent = 'Copy';
                    button.style.backgroundColor = '#28a745'; 
                }, 2000);
            });
        }
    </script>

3. Additional Steps and Considerations

  1. Removing Redundant Functions

    • Any old functions (copyWithExecCommand) should be removed to prevent conflicts with the new consolidated function.
  2. Fallback Support

    • To support older browsers that do not recognize navigator.clipboard, consider adding a fallback using document.execCommand('copy').
  3. Styling and Responsiveness

    • A .simple-table class can maintain consistent spacing and borders.
    • Media queries (e.g., @media (max-width: 600px)) can convert the two-column layout into a stacked view on smaller screens.
  4. Accessibility

    • Adding aria-label to the Copy button can improve screen-reader compatibility:
    <button class="copy-button" onclick="copyToClipboard_enhanced(this)" aria-label="Copy content">Copy</button>
  5. Proofreading and Testing

    • Verifying that each button correctly copies the intended <pre> block.
    • Checking for console errors in the browser’s developer tools.
    • Ensuring that the text is copied properly by pasting into an external editor.

4. Example of the Updated Layout

Below is a concise illustration of the table and script for reference:

Sample Text for Copy
<script>
    function copyToClipboard_enhanced(button) {
        // Consolidated copy function as described above
    }
</script>

Written on December 31th, 2024


Dotted Border Box Styling Options

Inline CSS can be used to create a box with a dotted border. One common approach is to use the style attribute with properties such as border, padding, and border-radius. The following examples show various color options for a dotted border box.

<div style="border: 3px dotted #66b266; padding: 10px; border-radius: 5px;">
  Your content here.
</div>

Below are additional color options for the dotted border box:

Option Color Code Example
Option 1 #66b266 (Medium Green)
Content here.
Option 2 #008000 (Dark Green)
Content here.
Option 3 #006400 (Very Dark Green)
Content here.
Option 4 #2e8b57 (Sea Green)
Content here.

Written on March 14, 2025


Collapsible Menu with <details> and <summary>

The <details> and <summary> elements provide a simple way to create collapsible or expandable sections. When the user clicks the <summary> (the visible heading), the associated content in <details> toggles between hidden and shown. This is helpful for creating "tear-down" menus or sections that can be revealed on demand.

<details>
  <summary></summary>
</details>

Here’s a slightly more customized example that adds inline CSS for spacing and cursors:

<details style="margin: 10px 0; padding: 10px; border: 1px solid #ccc;">
  <summary style="cursor: pointer; font-weight: bold;">Toggle Menu</summary>
  <p>Details shown after toggling.</p>
  <ul>
    <li>Item 1</li>
    <li>Item 2</li>
    <li>Item 3</li>
  </ul>
</details>

With <details> and <summary>, you can cleanly hide content until the user requests it, keeping your page organized and improving the user experience.

Written on March 29, 2025


Videos


Comparing Video Modals and Embedded Videos

Video modals and embedded videos represent two distinct approaches to presenting video content within a webpage. While both rely on the HTML <video> element to display media, their methods of delivery, user interaction, and overall user experience differ significantly. Understanding these differences and learning how to implement each method enables more effective decision-making and design strategies.

Aspect Video Modal Embedded Video
Presentation Displayed in an overlay, above the main page content. Integrated directly into the page's layout, within standard content flow.
User Focus Focuses the viewer’s attention by dimming background elements. Background content remains visible, potentially competing for attention.
Interaction Requires user action to open or close. Often involves clicking a button or thumbnail to initiate the modal. Immediately visible on the page; viewers may simply scroll to play and watch the video.
Implementation Often involves HTML, CSS, and JavaScript for modal functionality. Additional JS handles opening, closing, and focusing the video. Primarily requires embedding a <video> element into the HTML structure. Advanced styling or interaction is optional.
Responsiveness Uses inline styles, flexible sizing, and container constraints to adapt seamlessly to various screen sizes. Also can be responsive using inline styles or CSS, but remains part of the main layout, potentially affecting overall page structure.
Accessibility Can incorporate <figcaption>, <track> captions, and ARIA attributes to ensure inclusive viewing. The modal structure itself may require extra ARIA roles and attributes. Similarly can use captions and ARIA attributes for accessibility. Embedded videos may be simpler to navigate for screen readers since they are in the normal reading flow.
Performance Can implement lazy loading techniques, not loading video until the modal opens. This may reduce initial page load. Embedded videos may load as the page loads, potentially increasing initial load times unless deferred or lazy-loaded via JavaScript.
Use Cases Ideal for highlighted content, promotional media, or providing a more controlled viewing experience without leaving the current page view. Suitable for situations where continuous access to the video is desirable, such as tutorials or background videos integrated into the page’s narrative flow.

Defining Embedded Videos and Video Modals

  1. Embedded Videos

    Embedded videos are placed directly into the page’s layout, appearing among other textual or visual elements. They are immediately visible, often encouraging viewers to interact with them as part of the normal scrolling experience. This simplicity allows quick access but may result in less focused attention if the page content is dense.

    • Seamless integration into the page layout.
    • Always visible without additional user actions.
    • Straightforward HTML structure, often just a <video> element with sources.
    • Can be styled and resized using inline styles or CSS.
    • Suitable for scenarios where continuous access to the video is desired.
  2. Video Modals

    Video modals present videos in an overlay that appears above the page’s main content when triggered. This approach reduces distractions by dimming background elements and focusing the viewer’s attention on the video. Although it requires more scripting and user interaction, it can provide a more immersive viewing experience.

    • Displayed in a pop-up overlay window.
    • Dims or blurs background content, enhancing focus on the video.
    • Requires user action (e.g., clicking a button) to open and close.
    • Provides a controlled viewing environment.
    • Ideal for promotional content, featured media, or highlighting special video assets.

Illustrative Code Examples

  1. Embedded Video Example

    The following code snippet demonstrates a basic embedded video. The video is part of the page flow, using inline styling to ensure responsive sizing. This example can be expanded with multiple sources, captions, and attributes as needed.

    <!-- Embedded Video Example -->
    <video style="width:100%; height:auto;" controls>
      <source src="videos/example.mp4" type="video/mp4">
      <source src="videos/example.webm" type="video/webm">
      <!-- Optional Caption Track -->
      <track kind="captions" src="videos/captions.vtt" srclang="en" label="English">
      Your browser does not support the video tag.
    </video>
    
    • controls: Provides playback controls (play, pause, volume, fullscreen).
    • Multiple <source> elements: Ensures compatibility with different browsers.
    • <track> element: Adds captions for improved accessibility.
    • Inline styles: Manages responsive sizing without external CSS interference.
  2. Video Modal Example

    Below is a sample video modal implementation. This example uses a trigger button to open a modal overlay containing the video, ensuring user focus remains on the media when it plays.

    <!-- Trigger Button -->
    <button id="openModal" style="padding:10px 20px; font-size:16px;">Watch Video</button>
    
    <!-- Modal Structure -->
    <div id="videoModal" style="
      display:none; position:fixed; z-index:1000; 
      left:0; top:0; width:100%; height:100%; 
      overflow:auto; background-color:rgba(0,0,0,0.8);
    " aria-hidden="true" role="dialog" aria-labelledby="modalTitle">
    
      <!-- Modal Content -->
      <div style="
        position:relative; margin:5% auto; padding:0; 
        width:80%; max-width:700px;
      ">
        <!-- Close Button -->
        <span id="closeModal" style="
          position:absolute; top:10px; right:25px; 
          color:#fff; font-size:35px; font-weight:bold; 
          cursor:pointer;
        ">&times;</span>
    
        <!-- Video in Modal -->
        <video style="width:100%; height:auto;" controls>
          <source src="videos/example.mp4" type="video/mp4">
          <track kind="captions" src="videos/captions.vtt" srclang="en" label="English">
          Your browser does not support the video tag.
        </video>
    
        <!-- Caption -->
        <figcaption style="color:#fff; text-align:center; margin-top:10px;">
          <em>Example video demonstrating modal overlay and inline styling.</em>
        </figcaption>
      </div>
    </div>
    
    <!-- JavaScript for Modal Functionality -->
    <script>
      const modal = document.getElementById('videoModal');
      const openBtn = document.getElementById('openModal');
      const closeBtn = document.getElementById('closeModal');
    
      openBtn.onclick = function() {
        modal.style.display = 'block';
      }
    
      closeBtn.onclick = function() {
        modal.style.display = 'none';
        const video = modal.querySelector('video');
        video.pause();
        video.currentTime = 0;
      }
    
      window.onclick = function(event) {
        if (event.target === modal) {
          modal.style.display = 'none';
          const video = modal.querySelector('video');
          video.pause();
          video.currentTime = 0;
        }
      }
    
      // Optional: Close modal with Esc key
      document.addEventListener('keydown', function(event) {
        if (event.key === 'Escape') {
          modal.style.display = 'none';
          const video = modal.querySelector('video');
          video.pause();
          video.currentTime = 0;
        }
      });
    </script>
    
    • Trigger Button: Opens the modal on click, displaying the overlay and the video.
    • Modal Structure: Uses position:fixed, width:100%, and height:100% to cover the entire viewport. The semi-transparent background (rgba(0,0,0,0.8)) dims the main content.
    • Close Button & Event Handling: The × icon, clicking outside the video area, or pressing the Esc key closes the modal. Upon closing, the script pauses and resets the video.
    • ARIA Attributes: role="dialog", aria-hidden="true", and aria-labelledby="modalTitle" enhance accessibility by providing context for assistive technologies.

Written on December 20th, 2024


Video Modal


Implementing an Automatic Video Modal with Adjustable Width

Creating an engaging user experience often involves presenting multimedia content in an accessible and aesthetically pleasing manner. This guide outlines the process of implementing an automatic pop-up (modal) window that displays a descriptive paragraph alongside an autoplaying video. The video is centered within the modal and defaults to a width of 70%, with the ability for users to adjust its size via a slider. The implementation ensures responsiveness and accessibility across various devices and browsers.

  1. Structure the HTML

    Begin by setting up the HTML structure, including the modal container, content, paragraph, slider, and video elements.

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>iOS App Demo Video</title>
        <!-- CSS will be added here -->
    </head>
    <body>
    
        <!-- Modal Structure -->
        <div id="video-modal" class="modal">
            <div class="modal-content">
                <span class="close">&times;</span>
                
                <!-- Descriptive Paragraph -->
                <p class="demo-paragraph">
                    This is a demo video for the upcoming iOS app, currently experiencing technical issues with submission to the Apple App Store.
                </p>
                
                <!-- Slider for Adjusting Video Width -->
                <div class="slider-container">
                    <label for="width-slider">Adjust video width: <span id="slider-value">70%</span></label>
                    <input 
                        type="range" 
                        id="width-slider" 
                        min="50" 
                        max="100" 
                        value="70" 
                        oninput="updateVideoWidth(this.value)">
                </div>
    
                <!-- Video Container -->
                <div class="video-container">
                    <video 
                        id="demo-video" 
                        autoplay 
                        muted 
                        loop 
                        playsinline 
                        style="width: 70%;">
                        <source src="src/iOS_App_demo01.mp4" type="video/mp4">
                        Your browser does not support the video tag.
                    </video>
                </div>
            </div>
        </div>
    
        <!-- JavaScript will be added here -->
    
    </body>
    </html>
    
  2. Apply Styling with CSS

    Incorporate CSS to style the modal, its content, the paragraph, slider, and video to ensure proper alignment, responsiveness, and visual appeal.

    <style>
    /* Paragraph Styling */
    .demo-paragraph {
        text-align: left;
        font-size: 16px;
        line-height: 1.5;
        margin-bottom: 20px;
    }
    
    /* Modal Background */
    .modal {
        display: none; /* Initially hidden */
        position: fixed;
        z-index: 1000;
        left: 0;
        top: 0;
        width: 100%;
        height: 100%;
        overflow: auto;
        background-color: rgba(0,0,0,0.5); /* Semi-transparent background */
    }
    
    /* Modal Content Box */
    .modal-content {
        background-color: #fefefe;
        margin: 5% auto; /* Vertically and horizontally centered */
        padding: 20px;
        border: 1px solid #888;
        width: 80%;
        max-width: 800px;
        border-radius: 8px;
        position: relative;
        animation-name: animatetop;
        animation-duration: 0.4s;
        box-shadow: 0 5px 15px rgba(0,0,0,0.3);
    }
    
    /* Entrance Animation */
    @keyframes animatetop {
        from {transform: translateY(-50px); opacity: 0}
        to {transform: translateY(0); opacity: 1}
    }
    
    /* Close Button Styling */
    .close {
        color: #aaa;
        position: absolute;
        top: 10px;
        right: 20px;
        font-size: 28px;
        font-weight: bold;
        cursor: pointer;
    }
    
    .close:hover,
    .close:focus {
        color: black;
        text-decoration: none;
    }
    
    /* Prevent Background Scrolling When Modal is Open */
    body.modal-open {
        overflow: hidden;
    }
    
    /* Slider Container Styling */
    .slider-container {
        margin-bottom: 20px;
    }
    
    .slider-container label {
        display: block;
        margin-bottom: 10px;
        font-weight: bold;
    }
    
    .slider-container input[type="range"] {
        width: 100%;
    }
    
    /* Centering the Video */
    .video-container {
        display: flex;
        justify-content: center;
    }
    
    /* Responsive Video Styling */
    video {
        max-width: 100%;
        height: auto;
        border: 2px solid #ccc;
        border-radius: 10px;
    }
    </style>
    
  3. Implement Functionality with JavaScript

    Add JavaScript to handle the automatic opening of the modal upon page load, closing mechanisms, and dynamic adjustment of the video width based on the slider's value.

    <script>
    // Retrieve Modal and Close Button Elements
    const modal = document.getElementById('video-modal');
    const closeBtn = document.querySelector('.close');
    
    /**
     * Opens the modal and prevents background scrolling.
     */
    function openModal() {
        modal.style.display = 'block';
        document.body.classList.add('modal-open');
    }
    
    /**
     * Closes the modal, restores background scrolling, and resets the video.
     */
    function closeModal() {
        modal.style.display = 'none';
        document.body.classList.remove('modal-open');
        const video = document.getElementById('demo-video');
        video.pause();
        video.currentTime = 0;
    }
    
    /**
     * Updates the video width based on the slider value.
     * @param {number} value - The current value of the slider.
     */
    function updateVideoWidth(value) {
        const video = document.getElementById('demo-video');
        video.style.width = value + '%';
        document.getElementById('slider-value').textContent = value + '%';
    }
    
    /**
     * Initializes the modal to open automatically when the page loads.
     */
    window.onload = function() {
        openModal();
    }
    
    /**
     * Closes the modal when the close button is clicked.
     */
    closeBtn.onclick = function() {
        closeModal();
    }
    
    /**
     * Closes the modal when a click occurs outside the modal content.
     */
    window.onclick = function(event) {
        if (event.target == modal) {
            closeModal();
        }
    }
    
    /**
     * Allows closing the modal using the 'Escape' key for accessibility.
     */
    window.addEventListener('keydown', function(event) {
        if (event.key === 'Escape' && modal.style.display === 'block') {
            closeModal();
        }
    });
    </script>
    

Modal Pop-up with Interactive Features

This document provides a detailed explanation of the implementation of a modal pop-up window on a webpage. The modal incorporates various interactive elements, including an automatically repeating video, a slider for adjusting video width, multiple methods for closing the modal, and session persistence to enhance user experience. The guide is structured to facilitate easy understanding, reproduction, and maintenance of the modal's functionalities.


1. HTML Structure

The HTML structure establishes the foundational elements of the modal, encompassing the title, description, slider, checkbox, and video components. The modal is designed to be initially hidden and becomes visible based on user interactions or predefined conditions.

<!-- Modal Structure -->
<div id="video-modal" class="modal" aria-hidden="true" role="dialog" aria-labelledby="modal-title">
    <div class="modal-content" role="document" tabindex="-1">
        <!-- Close Button -->
        <span class="close" aria-label="Close Modal">×</span>
        
        <!-- Title -->
        <h2 class="modal-title" id="modal-title">Demo Video for the Upcoming iOS App</h2>
        
        <!-- Description Paragraph -->
        <p class="modal-description">
            Currently experiencing technical issues with submission to the Apple App Store, but expected to be released in 2025, in combination with Apple Vision Pro.
        </p>

        <!-- Slider for Adjusting Video Width -->
        <div class="slider-container">
            <label for="width-slider">Adjust video width: <span id="slider-value">50%</span></label>
            <div class="slider-wrapper">
                <input 
                    type="range" 
                    id="width-slider" 
                    min="30" 
                    max="100" 
                    value="50" 
                    step="10"
                    aria-valuemin="30"
                    aria-valuemax="100"
                    aria-valuenow="50"
                    aria-label="Adjust video width"
                    oninput="updateVideoWidth(this.value)">
            </div>
        </div>

        <!-- Checkbox to Prevent Modal from Showing Again in Session -->
        <div class="checkbox-container">
            <input type="checkbox" id="dont-show-checkbox" onclick="handleCheckbox(this)">
            <label for="dont-show-checkbox">Don't show this again</label>
        </div>

        <!-- Video Container -->
        <div class="video-container">
            <video 
                id="demo-video" 
                autoplay 
                muted 
                loop 
                playsinline 
                style="width: 50%;">
                <source src="src/iOS_App_demo01.mp4" type="video/mp4">
                Your browser does not support the video tag.
            </video>
        </div>
    </div>
</div>

2. CSS Styling

The CSS defines the visual presentation of the modal and its components, ensuring an aesthetically pleasing and responsive design. It handles the layout, animations, responsiveness, and overall user interface enhancements.

/* Modal Background */
.modal {
    display: none; /* Initially hidden */
    position: fixed;
    z-index: 1000;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    overflow: auto;
    background-color: rgba(0,0,0,0.5); /* Semi-transparent background */
}

/* Modal Content Box */
.modal-content {
    background-color: #fefefe;
    margin: 5% auto; /* Vertically and horizontally centered */
    padding: 20px;
    border: 1px solid #888;
    width: 80%;
    max-width: 800px;
    border-radius: 8px;
    position: relative;
    animation-name: animatetop;
    animation-duration: 0.4s;
    box-shadow: 0 5px 15px rgba(0,0,0,0.3);
    outline: none; /* Remove default outline */
}

/* Entrance Animation */
@keyframes animatetop {
    from {transform: translateY(-50px); opacity: 0}
    to {transform: translateY(0); opacity: 1}
}

/* Close Button Styling */
.close {
    color: #aaa;
    position: absolute;
    top: 10px;
    right: 20px;
    font-size: 28px;
    font-weight: bold;
    cursor: pointer;
}

.close:hover,
.close:focus {
    color: black;
    text-decoration: none;
}

/* Prevent Background Scrolling When Modal is Open */
body.modal-open {
    overflow: hidden;
}

/* Slider Container Styling */
.slider-container {
    margin-bottom: 20px;
    display: flex;
    align-items: center;
}

.slider-container label {
    display: block;
    margin-bottom: 10px;
    font-weight: bold;
    flex: 1;
}

.slider-wrapper {
    width: 66%; /* Set slider to 2/3 of the modal width */
}

.slider-wrapper input[type="range"] {
    width: 100%;
}

/* Checkbox Container Styling */
.checkbox-container {
    margin-bottom: 20px;
    display: flex;
    justify-content: flex-end; /* Align to the right */
    align-items: center;
}

.checkbox-container input[type="checkbox"] {
    margin-right: 10px;
}

/* Title Styling */
.modal-title {
    font-size: 24px;
    font-weight: bold;
    margin-bottom: 15px;
    text-align: center;
}

/* Description Styling */
.modal-description {
    text-align: center;
    font-size: 16px;
    margin-bottom: 20px;
}

/* Centering the Video */
.video-container {
    display: flex;
    justify-content: center;
}

/* Responsive Video Styling */
video {
    max-width: 100%;
    height: auto;
    border: 2px solid #ccc;
    border-radius: 10px;
}

/* Responsive Adjustments */
@media (max-width: 600px) {
    .modal-content {
        width: 90%;
    }
    .slider-wrapper {
        width: 100%;
    }
    .checkbox-container {
        justify-content: center;
    }
}

3. JavaScript Functionality

The JavaScript code orchestrates the interactive behavior of the modal, managing its visibility, user interactions, and state persistence. It ensures a seamless and intuitive user experience by handling events and dynamically updating the modal's properties.

<script>
    // Retrieve Modal and Close Button Elements
    const modal = document.getElementById('video-modal');
    const closeBtn = document.querySelector('.close');

    /**
     * Opens the modal and prevents background scrolling.
     */
    function openModal() {
        modal.style.display = 'block';
        modal.setAttribute('aria-hidden', 'false');
        document.body.classList.add('modal-open');
        // Set focus to the modal for accessibility
        modal.querySelector('.modal-content').focus();
    }

    /**
     * Closes the modal, restores background scrolling, and resets the video.
     */
    function closeModal() {
        modal.style.display = 'none';
        modal.setAttribute('aria-hidden', 'true');
        document.body.classList.remove('modal-open');
        const video = document.getElementById('demo-video');
        video.pause();
        video.currentTime = 0;
    }

    /**
     * Updates the video width based on the slider value.
     * @param {number} value - The current value of the slider.
     */
    function updateVideoWidth(value) {
        const video = document.getElementById('demo-video');
        video.style.width = value + '%';
        document.getElementById('slider-value').textContent = value + '%';
        // Update ARIA attribute for accessibility
        const slider = document.getElementById('width-slider');
        slider.setAttribute('aria-valuenow', value);
    }

    /**
     * Handles the checkbox state to prevent modal from showing again in the session.
     * If checked, also closes the modal.
     * @param {HTMLInputElement} checkbox - The checkbox element.
     */
    function handleCheckbox(checkbox) {
        if (checkbox.checked) {
            sessionStorage.setItem('hideVideoModal', 'true');
            closeModal(); // Close the modal immediately when checked
        } else {
            sessionStorage.removeItem('hideVideoModal');
        }
    }

    /**
     * Initializes the modal to open automatically when the page loads,
     * unless the user has opted not to show it.
     */
    window.addEventListener('DOMContentLoaded', () => {
        const hideModal = sessionStorage.getItem('hideVideoModal');
        if (!hideModal) {
            openModal();
        }
    });

    /**
     * Closes the modal when the close button is clicked.
     */
    closeBtn.addEventListener('click', closeModal);

    /**
     * Closes the modal when a click occurs outside the modal content.
     */
    modal.addEventListener('click', function(event) {
        if (event.target === modal) {
            closeModal();
        }
    });

    /**
     * Prevents clicks inside modal-content from closing the modal.
     */
    const modalContent = document.querySelector('.modal-content');
    modalContent.addEventListener('click', function(event) {
        event.stopPropagation();
    });

    /**
     * Allows closing the modal using the 'Escape' key for accessibility.
     */
    window.addEventListener('keydown', function(event) {
        if (event.key === 'Escape' && modal.style.display === 'block') {
            closeModal();
        }
    });

    /**
     * Implements focus trapping within the modal for accessibility.
     */
    modal.addEventListener('keydown', function(event) {
        const focusableElements = modal.querySelectorAll('a[href], button, textarea, input, select, [tabindex]:not([tabindex="-1"])');
        const firstElement = focusableElements[0];
        const lastElement = focusableElements[focusableElements.length - 1];

        if (event.key === 'Tab') {
            if (event.shiftKey) { // Shift + Tab
                if (document.activeElement === firstElement) {
                    event.preventDefault();
                    lastElement.focus();
                }
            } else { // Tab
                if (document.activeElement === lastElement) {
                    event.preventDefault();
                    firstElement.focus();
                }
            }
        }
    });
</script>
  1. Modal Control:
    • openModal(): Displays the modal, prevents background scrolling, and sets focus for accessibility.
    • closeModal(): Hides the modal, restores background scrolling, and resets the video playback.
  2. Slider Interaction:
    • updateVideoWidth(value): Adjusts the video's width based on the slider's value and updates the displayed percentage. It also updates the ARIA attribute to reflect the current value for assistive technologies.
  3. Checkbox Handling:
    • handleCheckbox(checkbox): When the checkbox is selected, it stores a flag in sessionStorage to prevent the modal from appearing again during the session and closes the modal immediately.
  4. Initialization:
    • On page load (DOMContentLoaded), the script checks sessionStorage to determine whether to display the modal.
  5. Event Listeners:
    • Close Button Click: Closes the modal.
    • Outside Click: Clicking outside the modal content closes the modal.
    • Inside Click: Prevents clicks inside the modal content from propagating and closing the modal.
    • Escape Key: Pressing the 'Escape' key closes the modal.
    • Focus Trapping: Ensures that keyboard navigation (Tab key) remains within the modal for accessibility.

4. Implementation Details

This section delves into the specific implementation strategies employed to achieve the desired functionalities of the modal pop-up. Each subsection highlights the relevant code snippets and explains their roles in fulfilling the respective tasks.

4.1 Video Playback: Automatically Repeating Video

The video element is configured to play automatically upon the modal's appearance and to loop continuously, ensuring uninterrupted playback.

<!-- Video Container -->
<div class="video-container">
    <video 
        id="demo-video" 
        autoplay 
        muted 
        loop 
        playsinline 
        style="width: 50%;">
        <source src="src/iOS_App_demo01.mp4" type="video/mp4">
        Your browser does not support the video tag.
    </video>
</div>

4.2 Slider Control: Adjusting Video Width

A slider is integrated to allow users to adjust the video's width in increments of 10%, within a range of 30% to 100%. The default width is set to 50%.

<!-- Slider for Adjusting Video Width -->
<div class="slider-container">
    <label for="width-slider">Adjust video width: <span id="slider-value">50%</span></label>
    <div class="slider-wrapper">
        <input 
            type="range" 
            id="width-slider" 
            min="30" 
            max="100" 
            value="50" 
            step="10"
            aria-valuemin="30"
            aria-valuemax="100"
            aria-valuenow="50"
            aria-label="Adjust video width"
            oninput="updateVideoWidth(this.value)">
    </div>
</div>
/**
 * Updates the video width based on the slider value.
 * @param {number} value - The current value of the slider.
 */
function updateVideoWidth(value) {
    const video = document.getElementById('demo-video');
    video.style.width = value + '%';
    document.getElementById('slider-value').textContent = value + '%';
    // Update ARIA attribute for accessibility
    const slider = document.getElementById('width-slider');
    slider.setAttribute('aria-valuenow', value);
}

4.3 Multiple Close Methods

The modal incorporates three distinct methods for closure: clicking the "X" button, clicking outside the modal content area, and selecting the provided checkbox.

<!-- Close Button -->
<span class="close" aria-label="Close Modal">×</span>
/**
 * Closes the modal when the close button is clicked.
 */
closeBtn.addEventListener('click', closeModal);

/**
 * Closes the modal when a click occurs outside the modal content.
 */
modal.addEventListener('click', function(event) {
    if (event.target === modal) {
        closeModal();
    }
});

/**
 * Handles the checkbox state to prevent modal from showing again in the session.
 * If checked, also closes the modal.
 * @param {HTMLInputElement} checkbox - The checkbox element.
 */
function handleCheckbox(checkbox) {
    if (checkbox.checked) {
        sessionStorage.setItem('hideVideoModal', 'true');
        closeModal(); // Close the modal immediately when checked
    } else {
        sessionStorage.removeItem('hideVideoModal');
    }
}
  1. Closing via "X" Button:
    • The close button (<span class="close">×</span>) is equipped with an event listener that triggers the closeModal function upon being clicked.
  2. Closing by Clicking Outside Modal Content:
    • An event listener on the modal container detects clicks outside the modal content area. If such a click is detected (i.e., the event target is the modal itself), the closeModal function is invoked.
  3. Closing via Checkbox Selection:
    • The checkbox (<input id="dont-show-checkbox">) is linked to the handleCheckbox function through the onclick attribute.
    • When the checkbox is selected, the modal is closed immediately by calling closeModal, and a flag is set in sessionStorage to prevent the modal from reappearing during the current session.

4.4 Session Persistence: Preventing Modal Reappearance

To enhance user experience, the modal incorporates a checkbox that, when selected, prevents the modal from appearing again during the current browsing session.

<!-- Checkbox to Prevent Modal from Showing Again in Session -->
<div class="checkbox-container">
    <input type="checkbox" id="dont-show-checkbox" onclick="handleCheckbox(this)">
    <label for="dont-show-checkbox">Don't show this again</label>
</div>
/**
 * Handles the checkbox state to prevent modal from showing again in the session.
 * If checked, also closes the modal.
 * @param {HTMLInputElement} checkbox - The checkbox element.
 */
function handleCheckbox(checkbox) {
    if (checkbox.checked) {
        sessionStorage.setItem('hideVideoModal', 'true');
        closeModal(); // Close the modal immediately when checked
    } else {
        sessionStorage.removeItem('hideVideoModal');
    }
}

/**
 * Initializes the modal to open automatically when the page loads,
 * unless the user has opted not to show it.
 */
window.addEventListener('DOMContentLoaded', () => {
    const hideModal = sessionStorage.getItem('hideVideoModal');
    if (!hideModal) {
        openModal();
    }
});

Embedded Video


Embedding video content on webpages can be achieved using the native HTML5 <video> element for self-hosted files or by using embedded iframes for platforms like YouTube. This guide provides a comprehensive overview of both approaches, covering responsive design, autoplay policies, performance optimizations, accessibility, and solutions for dealing with YouTube embed restrictions. The aim is to offer ready-to-use templates and best practices for each scenario.

  1. HTML5 <video> Embedding

    The HTML5 <video> element allows you to embed video files directly into a webpage without relying on external plugins. It supports multiple source formats and provides built-in playback controls, making it the standard choice for self-hosted videos. Below is a basic example of embedding a video with two source formats and a fallback message:

    <video controls width="640" height="360" poster="placeholder.jpg">
      <source src="video.mp4" type="video/mp4">
      <source src="video.webm" type="video/webm">
      Your browser does not support the video tag.
    </video>
    

    Explanation: The <video> element above includes controls so that the browser’s native play/pause and volume UI is shown. The width and height attributes set the display size in pixels (640×360 in this case). A poster image is specified, which will be shown as a placeholder before the video starts. Inside the <video> element, multiple <source> elements are provided: the browser will use the first source with a supported format (here, MP4 then WebM). The text “Your browser does not support the video tag.” will display only if the browser cannot play any provided format or doesn’t support HTML5 video at all.

    In practice, you should include at least an MP4 source (using H.264/AAC encoding) for maximum compatibility, since all modern browsers support MP4. Optionally, providing a WebM source (VP8/VP9) can offer better compression in browsers that support it. Older formats like Ogg Theora can be included for legacy support, but they are largely unnecessary today. The width and height attributes are also optional; you can omit them and use CSS to control sizing (which is preferable for responsive layouts, discussed later).

    Common <video> attributes:

    • controls: Displays the browser’s default video controls (play/pause, seek bar, volume, etc.). Without this, no controls are shown unless you implement custom ones via JavaScript.
    • autoplay: Starts playing the video automatically upon page load. Note that most browsers will only autoplay if the video is muted (see Autoplay and Browser Restrictions section).
    • muted: Mutes the audio by default. This is required for autoplay to work in modern browsers and is useful for background videos where sound isn’t desired.
    • loop: Causes the video to restart from the beginning after reaching the end, playing in a continuous loop.
    • poster: URL of an image to show before the video plays (or while downloading). Helps provide a preview frame instead of a black or blank box.
    • preload: Suggests how much of the file to preload. Values can be "auto" (let the browser decide or preload the whole video), "metadata" (preload only video metadata like duration), or "none" (do not preload anything until user hits play). Proper use can improve performance.
    • playsinline: Especially for mobile devices, this attribute allows the video to play inline within the page. On iPhones, for example, a video without playsinline might automatically enter fullscreen playback.
    • controlsList: A non-standard attribute (supported in some browsers like Chrome) to control which controls are shown. For instance, controlsList="nodownload" can hide the download option from the context menu.
    • crossorigin: If the video file is hosted on a different domain and you need to manipulate it with canvas or JavaScript (for example, to apply filters or analytics), set crossorigin="anonymous" and ensure the server provides appropriate CORS headers. This avoids cross-domain security issues.

    Using the native <video> element has the advantage of being plugin-free and under your control. You can style it with CSS (to an extent) and even build custom controls using JavaScript by interacting with the video’s API (e.g., play(), pause(), etc.). However, creating fully custom video players can be complex, so for basic usage the default controls are recommended. In older web development (before HTML5), videos were often embedded using Flash or other plugins via <object>/<embed> tags – those methods are now obsolete in favor of the HTML5 approach demonstrated above.

  2. YouTube <iframe> Embedding

    Instead of self-hosting video files, you can embed videos from platforms like YouTube using an <iframe>. This is often convenient because YouTube handles video encoding, player UI, and bandwidth. The basic approach is to use YouTube’s embed URL within an iframe. Here is a typical example of embedding a YouTube video:

    <iframe
      width="560"
      height="315"
      src="https://www.youtube.com/embed/VIDEO_ID?playsinline=1"
      title="YouTube video player"
      frameborder="0"
      allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
      allowfullscreen
    ></iframe>
    

    Explanation: Replace VIDEO_ID in the src URL with the unique identifier of the YouTube video you want to embed (this is the part after v= in a normal YouTube link). In the example above, width and height define the embedded player’s size in pixels (560×315 is a common default for a 16:9 video). The title attribute provides an accessible label for the iframe (important for screen readers). The frameborder="0" attribute removes the old-style border around the iframe (though in HTML5 this can also be achieved with CSS). The allowfullscreen attribute permits the video to be viewed in fullscreen mode when the user clicks the fullscreen button.

    The allow attribute in the iframe is set with a list of allowed features: this example includes autoplay, encrypted-media, clipboard-write, accelerometer, gyroscope, and picture-in-picture. These enable the corresponding functionalities inside the iframe (for instance, allowing the video to autoplay if otherwise permitted, enabling the clipboard copy feature on YouTube’s UI, or allowing the video to go into Picture-in-Picture mode on supporting browsers). The ?playsinline=1 query parameter we added in the URL is a hint for iOS devices to play the video inline within the page instead of forcing fullscreen playback.

    YouTube iframes can be further customized via URL parameters. Here are some common YouTube embed query parameters and their effects:

    URL Parameter Effect
    autoplay=1 Start playing the video automatically. (Note: The video will usually need to be muted or user-interaction will be required due to browser autoplay policies.)
    mute=1 Start the video with sound muted. This is often used in conjunction with autoplay to allow the video to play without user interaction.
    controls=0 Hide the YouTube player controls (play/pause buttons, etc.). Use this if you want to provide your own controls or have a non-interactive video display. (Default is 1, which shows controls.)
    rel=0 When the video finishes, show related videos from the same channel only, rather than random suggestions. Prior to 2018, rel=0 would hide all related videos, but now it’s limited to the same channel.
    loop=1 Loop the video. If using this for a single video, you must also add playlist=[VIDEO_ID] (the same video’s ID) in the URL to make looping work.
    playlist=VIDEO_IDs A comma-separated list of video IDs to play in sequence. Often used along with loop=1 to loop a single video or to create a playlist of multiple videos in one embed.
    modestbranding=1 Removes the YouTube logo from the control bar (a small YouTube text will still appear, but the big logo watermark is hidden).
    start=30 Start playback at 30 seconds into the video (replace 30 with any number of seconds).
    end=60 Stop playback at 60 seconds (the video will stop at that point instead of playing to the end).
    cc_load_policy=1 Show closed captions (subtitles) by default if the video has them.
    cc_lang_pref=en Set the default captions language to English (using ISO language code, here "en"). You can change the code for other languages as needed.
    playsinline=1 Allows inline playback on mobile devices (particularly iOS). We included this in the example iframe URL to avoid fullscreen enforcement on iPhones.
    enablejsapi=1 Enables the JavaScript API. Required if you plan to control the player via JavaScript (using YouTube IFrame Player API). When using this, you should also specify an origin parameter (your domain) for security.

    By combining these parameters in the src URL, you can tailor the embed to your needs. For example, to embed a video that autoplays muted and loops continuously without showing controls or related videos, your src could look like: https://www.youtube.com/embed/VIDEO_ID?autoplay=1&mute=1&loop=1&playlist=VIDEO_ID&controls=0&rel=0. It’s generally best to only use the parameters you need, as too many can complicate the URL.

    Privacy Consideration: If you need to minimize tracking, YouTube offers a privacy-enhanced embed mode. This is achieved by using the youtube-nocookie.com domain for the embed. For instance: src="https://www.youtube-nocookie.com/embed/VIDEO_ID". In this mode, YouTube will not set cookies until the user interacts with the video (though their IP may still be transmitted to load the video). This can help with privacy compliance, but functionally the video will appear and play the same way.

    One thing to be aware of is that some YouTube videos may not allow embedding on external sites. If the video’s owner has disabled embedding, the player will display a message like “Video unavailable” or prompt the user to watch it on YouTube. Similarly, age-restricted content might not play in an iframe without user login. In the next sections, we will explore how to handle responsive design for video embeds, as well as strategies for autoplay, performance, accessibility, and dealing with such restrictions.

Responsiveness Techniques for Embedded Videos

By default, an embedded video (whether an HTML5 video element or a YouTube iframe) with fixed width and height will not automatically resize on smaller screens. To ensure videos look good on all devices (desktop, tablet, mobile), you need to make the embed responsive – i.e., able to scale proportionally to the width of its container. There are two primary approaches to achieve responsive videos:

  1. Using a Wrapper and Percentage Padding (Classic Method)

    This technique wraps the video element (or iframe) in a container div, which uses CSS to enforce a specific aspect ratio by using percentage padding. It has been a long-standing solution and works across all browsers. The trick is to give the container a bottom padding that is a percentage of its width (which defines the aspect ratio), then absolutely position the video inside it. For example, for a 16:9 aspect ratio video, use 56.25% padding (since 9/16 = 0.5625).

    <!-- HTML -->
    <div class="video-container">
      <iframe src="https://www.youtube.com/embed/VIDEO_ID" frameborder="0" allowfullscreen></iframe>
    </div>
    
    <!-- CSS -->
    <style>
      .video-container {
        position: relative;
        width: 100%;
        height: 0;
        padding-bottom: 56.25%; /* 16:9 aspect ratio */
      }
      .video-container iframe,
      .video-container video {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
      }
    </style>
    

    Explanation: The .video-container div is set to position: relative; and given width: 100%; (so it will scale to its parent’s width) but a fixed height: 0. The crucial part is padding-bottom: 56.25%;, which creates an internal space that is 56.25% of the container’s width, effectively enforcing a 16:9 ratio (for a different aspect ratio, adjust this percentage accordingly: e.g., 75% for 4:3 videos). The video iframe (or a <video> tag, which we also targeted in the CSS) is then absolutely positioned to fill this container (100% width and height, with top:0 and left:0). This way, as the container’s width changes (for example, on a smaller screen), its height adjusts to maintain the aspect ratio, and the video inside scales up or down to fit.

    You can use this method for multiple videos by reusing the video-container class for each video. It ensures your embed is fluid. Note that in this approach, the width of the video will never exceed the container’s width (often the container might be constrained by a parent element’s max-width). If you want to limit how large the video can get, you can add a max-width to the .video-container (for instance, max-width: 800px; to not grow beyond 800px even on big screens). The aspect ratio will still hold at smaller sizes down to very narrow widths.

  2. Using CSS aspect-ratio (Modern Method)

    Newer CSS allows specifying an aspect ratio directly, eliminating the need for a wrapper div. This method is cleaner since you can apply it to the <iframe> or <video> element itself. All modern browsers support aspect-ratio (though Internet Explorer does not, which is generally no longer a concern). To use this, set the element to width 100% and define its aspect ratio in CSS:

    /* CSS */
    .responsive-video {
      width: 100%;
      aspect-ratio: 16/9;
      /* Optional: ensure it doesn't overflow its container */
      max-width: 100%;
    }
    

    In your HTML, give the video iframe (or <video>) a class of responsive-video (or you can target it via other selectors) and don’t hardcode the height attribute. The CSS above will make the element take the full width of its container and automatically calculate the height using a 16:9 ratio. The max-width: 100% ensures it doesn’t overflow its container (which can be useful if the container has padding or other styling). If you need a different ratio, simply change 16/9 to your desired width/height ratio (for example 4/3 for 4:3).

    This modern method achieves the same result as the padding hack but with less markup and more clarity. However, if you need to support very old browsers, you might still fall back to the wrapper technique. Otherwise, aspect-ratio is the recommended approach for simplicity.

    In summary, a responsive video embed can be implemented either by using a containing element to enforce aspect ratio (compatible with essentially all browsers), or by leveraging the aspect-ratio CSS property for a cleaner solution in modern environments. Both methods ensure that your videos resize gracefully on different screen sizes without distortion.

Written on April 26, 2025


Embedding and Optimizing HTML5 Video

This document provides an integrated and refined overview of various methods and best practices for embedding video content using the HTML5 <video> element. It consolidates multiple approaches, ensuring that all discussed features, attributes, stylistic considerations, and optimizations are included. This guide maintains a formal tone and presents information in a hierarchical, structured manner. All examples prefer inline styling to avoid interaction with external styling or other HTML files. The provided instructions, thoughts, and ideas are arranged cohesively to facilitate informed decision-making after reading through the material.

1. Basic HTML Structure and Attributes

Embedding a video with the HTML5 <video> element involves specifying one or more <source> elements and providing fallbacks. At its core, the simplest example includes only the <video> tag, a single source, and a fallback message:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Basic Video Embed</title>
</head>
<body>
  <video style="width:100px; height:auto;" autoplay loop muted>
    <source src="src/Sora_Medusa20241219.mp4" type="video/mp4">
    Your browser does not support the video tag.
  </video>
</body>
</html>

2. Responsive Embedding and Layout Considerations

To ensure the video adapts to different screen sizes, inline styles can be used to achieve a responsive layout. Applying width: 100%; height: auto; allows the video to scale according to its container:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Responsive Video Embed</title>
</head>
<body>
  <video style="width:100%; height:auto;" autoplay loop muted playsinline>
    <source src="src/Sora_Medusa20241219.mp4" type="video/mp4">
    Your browser does not support the video tag.
  </video>
</body>
</html>

To avoid external CSS interactions, inline styles can be maintained. For further refinement, a containing element (e.g., a <div> or <figure>) with its own max-width can limit the video’s maximum size while preserving responsiveness. For example:

<figure style="width:100%; max-width:800px; margin:0 auto;">
  <video style="width:100%; height:auto;" autoplay loop muted playsinline>
    <source src="src/Sora_Medusa20241219.mp4" type="video/mp4">
    Your browser does not support the video tag.
  </video>
</figure>

3. Multiple Video Formats for Wider Browser Support

To ensure compatibility across a range of browsers, multiple source formats can be included. Browsers will choose the first format they can play:

<video style="width:100%; height:auto;" autoplay loop muted playsinline>
  <source src="src/Sora_Medusa20241219.mp4" type="video/mp4">
  <source src="src/Sora_Medusa20241219.webm" type="video/webm">
  <source src="src/Sora_Medusa20241219.ogv" type="video/ogg">
  Your browser does not support the video tag.
</video>

4. Accessibility and Semantics

Semantically grouping a video with its caption enhances accessibility and SEO. The <figure> and <figcaption> elements are often used together. This approach allows the caption to describe the video’s content meaningfully. An italicized <em> can distinguish the text stylistically:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Embed Sora Video</title>
</head>
<body>

  <h1>My Sora Video</h1>

  <figure>
    <video style="width:100%; height:auto;" controls loop muted playsinline preload="metadata">
      <source src="src/Sora_Medusa20241219.mp4" type="video/mp4">
      Your browser does not support the video tag.
    </video>
    <figcaption>
      <em>Medusa with serpentine hair inside a Greek temple, embodying Greek mythology as she awaits her encounter with Perseus. Created using Sora OpenAI.</em>
    </figcaption>
  </figure>

</body>
</html>

Caption Refinement Considerations:

<video style="width:100%; height:auto;" controls loop muted playsinline preload="metadata">
  <source src="src/Sora_Medusa20241219.mp4" type="video/mp4">
  <track kind="captions" src="src/captions.vtt" srclang="en" label="English">
  Your browser does not support the video tag.
</video>

This ensures users with hearing impairments can access the content through captions.

5. Autoplay Behavior and Browser Policies

Modern browsers often block videos from autoplaying if they contain sound. Using the muted attribute is recommended to ensure autoplay functionality. If desired, the autoplay attribute can be added to start playback immediately, as long as the video remains muted. For user-driven control, consider removing autoplay and relying solely on controls.

6. Performance Optimization Techniques

Efficient loading of videos enhances user experience, especially on pages with multiple videos or limited bandwidth environments. Consider these methods:

Preload Settings:

Video Compression:

Compressing the video file before embedding reduces file size and improves load times. Tools such as HandBrake can help optimize the video without significantly sacrificing quality.

Lazy Loading with JavaScript:

For scenarios involving multiple videos, lazy loading prevents videos from loading until they are near or within the user’s viewport. A JavaScript Intersection Observer can detect when a video enters the viewport and then set the src dynamically:

<video 
  style="width:100%; height:auto;" 
  controls loop muted playsinline
  preload="none" 
  data-src="src/Sora_Medusa20241219.mp4"
  class="lazy-video"
>
  Your browser does not support the video tag.
</video>

<script>
  document.addEventListener("DOMContentLoaded", function() {
    const lazyVideos = document.querySelectorAll('video.lazy-video');

    if ("IntersectionObserver" in window) {
      let lazyVideoObserver = new IntersectionObserver(function(entries, observer) {
        entries.forEach(function(entry) {
          if (entry.isIntersecting) {
            let video = entry.target;
            video.src = video.dataset.src;
            video.load();
            video.classList.remove("lazy-video");
            lazyVideoObserver.unobserve(video);
          }
        });
      });

      lazyVideos.forEach(function(video) {
        lazyVideoObserver.observe(video);
      });
    } else {
      // Fallback for browsers without IntersectionObserver
      lazyVideos.forEach(function(video) {
        video.src = video.dataset.src;
        video.load();
      });
    }
  });
</script>

This approach ensures minimal initial page load overhead and only loads videos as needed.

Written on December 20th, 2024


How to Embed a Youtube Video and Ensure Visibility on External Website

This document presents a comprehensive and structured reference for two primary tasks:

  1. Embedding a YouTube video within a webpage using <iframe> code.
  2. Enabling embedding in the YouTube Studio so that the video can be viewed directly on external websites.

The content is divided into two main sections—Part A (Embedding Essentials) and Part B (YouTube Content Settings)—offering a clear hierarchy to help developers and content owners seamlessly integrate YouTube videos into their websites.


Part A: Embedding Essentials

When embedding a YouTube video, substituting a local <video> tag with an <iframe> tag is typically the best practice. This approach ensures:

  1. Basic Embed Code

    <iframe 
      src="https://www.youtube.com/embed/[VIDEO_ID]"
      title="Descriptive Title"
      frameborder="0"
      allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
      allowfullscreen
    ></iframe>
    
    • src: Accepts the embed URL in the format https://www.youtube.com/embed/VIDEO_ID.
    • title: Serves as an accessibility aid and describes the video content.
    • frameborder: Commonly set to 0 for a borderless frame.
    • allow: Grants permission for autoplay, clipboard access, and other features.
    • allowfullscreen: Enables full-screen functionality on supported devices.
  2. Responsive Design Tips

    Maintaining a 16:9 aspect ratio for modern video content is a common practice. Employing a CSS trick with padding-bottom ensures responsiveness across various screen sizes:

    <div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe 
        src="https://www.youtube.com/embed/[VIDEO_ID]" 
        style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"
        frameborder="0"
        allowfullscreen
      ></iframe>
    </div>
    
    • Container Setup
      • position: relative; padding-bottom: 56.25%;: Helps maintain the 16:9 ratio (56.25% = 9/16 × 100).
      • height: 0; overflow: hidden;: Ensures the container scales properly.
    • Absolute Positioning
      • position: absolute; top: 0; left: 0;: Anchors the <iframe> to the top-left corner.
      • width: 100%; height: 100%;: Stretches the <iframe> to match the container dimensions.
  3. Actual Example

    Below is a practical example that demonstrates embedding a specific YouTube video (gqth-OGlzS0) within a <figure> element, including a caption:

    <figure>
      <iframe
        style="width: 100%; height: 500px;"
        src="https://www.youtube.com/embed/gqth-OGlzS0"
        title="Medusa with serpentine hair inside a Greek temple"
        frameborder="0"
        allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
        allowfullscreen
      ></iframe>
      <figcaption>
        <em>Medusa with serpentine hair inside a Greek temple, embodying Greek mythology as she awaits her encounter with Perseus. Created using Sora OpenAI.</em>
      </figcaption>
    </figure>
    

    This example uses inline styling to establish a fixed width (100%) and height (500px). The <figcaption> provides descriptive information about the video content.


Part B: YouTube Content Settings for Allowing Embedding

Enabling Embedding in YouTube Studio

By default, some videos might restrict embedding, leading to the message:

“Playback on other websites has been disabled by the video owner”

To rectify this, Allow embedding must be enabled in the video’s settings.

┌────────────────────────────┐
│   Sign in to YouTube       │
└───────────────┬────────────┘
                ↓
┌──────────────────────────────────┐
│   Access YouTube Studio         │
└───────────────┬─────────────────┘
                ↓
┌──────────────────────────────────┐
│   Open Content (Videos)         │
└───────────────┬─────────────────┘
                ↓
┌──────────────────────────────────┐
│   Select the Desired Video       │
│   (Edit Details)                 │
└───────────────┬─────────────────┘
                ↓
┌──────────────────────────────────┐
│   Expand "More options"          │
│   Check "Allow embedding"        │
└───────────────┬─────────────────┘
                ↓
┌──────────────────────────────────┐
│   Save Changes                   │
└──────────────────────────────────┘
Privacy Setting Description Embedding Behavior
Public Available to everyone on YouTube. Embedding allowed if Allow embedding is set.
Unlisted Accessible only with a direct link. Embedding may still require Allow embedding.
Private Visible only to specified users or channels. Embedding generally disabled.

Additional Notes

  1. Age-Restricted Content: May be subject to embedding limitations due to policy.
  2. Copyright Restrictions: Copyright holders may disable embedding.
  3. Content Security Policy (CSP): Websites must allow www.youtube.com and youtube.com in their CSP to embed videos.

Written on December 25th, 2024


Responsive YouTube Embedding

Ensuring that embedded YouTube videos remain responsive and maintain their aspect ratio across various devices is a crucial aspect of modern web design. The padding-bottom hack is a widely adopted method for achieving a responsive video embed that preserves the video’s original aspect ratio. This guide provides a comprehensive explanation of this technique, its underlying principles, and a systematic approach to apply it to similar tasks. The discussion also integrates comparative insights with alternative methods and offers structured guidance for future reference.

The Padding-Bottom Technique

The padding-bottom method utilizes a container <div> element with CSS styling that maintains a consistent aspect ratio for the embedded <iframe>. The following code snippet demonstrates the technique:

<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
  <iframe 
    src="https://www.youtube.com/embed/M-8FksMVAdU?autoplay=0" 
    style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;" 
    frameborder="0" 
    allow="accelerometer; encrypted-media; gyroscope; picture-in-picture" 
    allowfullscreen>
  </iframe>
</div>

Rationale Behind the 56.25% Padding-Bottom

The value 56.25% is critical in maintaining a 16:9 aspect ratio, the standard for most YouTube videos:

Comparative Options and Considerations

While the padding-bottom hack is preferred for its broad compatibility and reliability, modern CSS offers an alternative using the aspect-ratio property. However, the padding-bottom method is favored for its proven effectiveness and minimal reliance on browser support for newer CSS features.

Feature Padding-Bottom Method Aspect-Ratio Property
Browser Compatibility Widely supported across all modern and older browsers Supported by most modern browsers; may require polyfills for older versions
Implementation Complexity Simple HTML and inline styles Simpler code when supported, but may not work in older browsers
Control over Aspect Ratio Manual calculation required for non-standard ratios Automatic ratio maintenance via CSS property

Applying the Technique to Future Tasks

This responsive embedding method can be adapted for other videos or iframes that require a fixed aspect ratio. The following steps summarize the approach:

  1. Determine the Desired Aspect Ratio: Calculate the appropriate padding-bottom percentage using the formula:
    padding-bottom = (height / width) * 100%
    For a 4:3 video, for example, this would be (3 ÷ 4) * 100% = 75%.
  2. Structure the Container: Use a <div> with the calculated padding-bottom, position: relative;, height: 0;, and overflow: hidden;.
  3. Embed the <iframe>: Within the container, position the <iframe> absolutely at top-left, set its width and height to 100%, and include necessary attributes such as frameborder, allowfullscreen, and source modifications like ?autoplay=0.

Written on January 14, 2025


Responsive YouTube Video Embed

This code snippet demonstrates how to embed a YouTube video in a responsive manner using inline CSS. In this example, an <iframe> element is used along with inline styling to ensure that the video:

The allowfullscreen attribute permits the video to be viewed in full-screen mode when the user requests it.

<iframe src="https://www.youtube.com/embed/bzQd86tBzek" 
        title="YouTube video player" 
        allowfullscreen 
        style="width: 100%; aspect-ratio: 16 / 9; border: 0;"></iframe>

This approach makes it easy to embed videos that automatically adapt to different screen sizes while retaining the proper display format without additional external CSS files.



<iframe
  src="https://www.youtube.com/embed/4HCv9tabFQ8?start=137"
  title="~~~~~~~~~"
  style="width: 100%; aspect-ratio: 16/9; border: 0;"
  allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
  allowfullscreen>
</iframe>

Written on April 16, 2025


How to Circumvent YouTube Embed Issues (Written April 16, 2025)

This document serves as a reference to recall the methods employed to address the situation in which a YouTube video displays "This video is unavailable" within an iframe, despite the direct link functioning properly in a browser. The strategies described below are intended to offer a range of approaches that may be implemented when encountering such restrictions.

  1. Clickable Thumbnail Overlay

    This approach displays a thumbnail image (typically using YouTube’s thumbnail URL) with an overlaid play button. Upon activation, the thumbnail may either redirect the visitor to the YouTube video page or dynamically replace the thumbnail with the embedded iframe.

    Option A: Redirect to YouTube

    <div id="video-container" style="position: relative; width: 100%; padding-bottom: 56.25%; cursor: pointer;">
      <!-- Use YouTube's thumbnail URL format -->
      <img src="https://img.youtube.com/vi/JoinXlIH8uo/hqdefault.jpg" alt="Video Thumbnail" style="position: absolute; width: 100%; height: 100%; object-fit: cover;">
      <!-- Optional overlay with a play icon -->
      <div style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center;">
        <img src="play-icon.png" alt="Play" style="width: 60px; height: 60px;">
      </div>
    </div>
    <script>
      document.getElementById("video-container").addEventListener("click", function() {
        // Redirect to the full YouTube video page
        window.location.href = "https://www.youtube.com/watch?v=JoinXlIH8uo&list=WL&index=19&t=24s";
      });
    </script>
        

    Option B: Load the Iframe Dynamically

    <div id="video-container" style="position: relative; width: 100%; padding-bottom: 56.25%; cursor: pointer;">
      <img src="https://img.youtube.com/vi/JoinXlIH8uo/hqdefault.jpg" alt="Video Thumbnail" style="position: absolute; width: 100%; height: 100%; object-fit: cover;">
      <div style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center;">
        <img src="play-icon.png" alt="Play" style="width: 60px; height: 60px;">
      </div>
    </div>
    <script>
      document.getElementById("video-container").addEventListener("click", function() {
        // Replace the thumbnail with the embed code
        this.innerHTML = '<iframe src="https://www.youtube.com/embed/JoinXlIH8uo?list=WL&index=19&start=24" ' +
                         'title="YouTube video player" allowfullscreen ' +
                         'style="position: absolute; width: 100%; height: 100%; border: 0;"></iframe>';
      });
    </script>
        
  2. Modal or Lightbox Display

    This method involves using a modal or lightbox to display the video. A hidden modal is prepared in advance, and when activated by a click on the thumbnail, the modal becomes visible with the embedded video. This approach maintains the visitor on the site and defers the loading of the iframe until after the interaction.

    <!-- Modal Container (initially hidden) -->
    <div id="videoModal" style="display: none; position: fixed; z-index: 1000; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0, 0, 0, 0.8);">
      <div style="margin: 10% auto; width: 80%; max-width: 800px; position: relative;">
        <!-- Close button -->
        <span id="closeModal" style="position: absolute; top: 10px; right: 20px; color: #fff; font-size: 30px; cursor: pointer;">×</span>
        <iframe src="" id="modalVideo" title="YouTube video player" allowfullscreen style="width: 100%; aspect-ratio: 16 / 9; border: 0;"></iframe>
      </div>
    </div>
    
    <!-- Thumbnail / Trigger -->
    <div id="openModal" style="position: relative; cursor: pointer; width: 100%; max-width: 800px; margin: auto;">
      <img src="https://img.youtube.com/vi/JoinXlIH8uo/hqdefault.jpg" alt="Video Thumbnail" style="width: 100%; display: block;">
      <div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);">
        <img src="play-icon.png" alt="Play" style="width: 60px; height: 60px;">
      </div>
    </div>
    
    <script>
      // Open modal and load video when the thumbnail is clicked
      document.getElementById("openModal").addEventListener("click", function() {
        document.getElementById("modalVideo").src = "https://www.youtube.com/embed/JoinXlIH8uo?list=WL&index=19&start=24";
        document.getElementById("videoModal").style.display = "block";
      });
    
      // Close modal when the close icon is clicked
      document.getElementById("closeModal").addEventListener("click", function() {
        document.getElementById("videoModal").style.display = "none";
        // Clear the src to stop the video when the modal is closed
        document.getElementById("modalVideo").src = "";
      });
    </script>
        
  3. User-Initiated Iframe Loading (On-Click Button)

    This strategy involves not preloading the iframe at all. Instead, a placeholder container and a "Play Video" button are provided. The embedded iframe is only loaded when the button is activated.

    <div id="videoContainer" style="width: 100%; aspect-ratio: 16 / 9; border: 1px solid #ccc;"></div>
    <button id="loadVideo" style="margin-top: 10px;">Play Video</button>
    
    <script>
      document.getElementById("loadVideo").addEventListener("click", function() {
        // Insert the iframe into the container when the button is clicked
        var container = document.getElementById("videoContainer");
        container.innerHTML = '<iframe src="https://www.youtube.com/embed/JoinXlIH8uo?list=WL&index=19&start=24" ' +
                              'title="YouTube video player" allowfullscreen style="width:100%; height:100%; border:0;"></iframe>';
        // Optionally hide the button after loading the video
        this.style.display = "none";
      });
    </script>
        

Written on April 16, 2025


Toggle


Reliable Methods for Storing Dev Mode Status in Web Applications

In web application development, maintaining consistent state across sessions and page loads is crucial. Inconsistencies in storing the "dev mode" status can lead to unpredictable behavior and hinder the development process. This document explores various reliable methods for storing the dev mode status, providing detailed sample code for each approach to aid in implementation.

Method Persistence Security Ease of Implementation Browser Support Data Size Limit
Local Storage Across Sessions Accessible via JS Simple Wide ~5MB
Session Storage Session Only Accessible via JS Simple Wide ~5MB
Cookies Configurable Sent with Requests Moderate Universal ~4KB
URL Parameters Per Request Visible in URL Simple N/A N/A
Server-Side Sessions Configurable Secure on Server Complex N/A Server-Defined

1. Using Local Storage

Local Storage allows for the storage of key-value pairs in the user's browser that persist even after the browser is closed and reopened. It is ideal for data that needs to be retained across sessions.

Implementation

Advantages

Considerations


2. Using Session Storage

Session Storage is similar to Local Storage but data is cleared when the page session ends, such as when the browser tab is closed. It is suitable for data that should not persist beyond the current session.

Implementation

Advantages

Considerations


3. Using Cookies

Cookies store small amounts of data with an expiration date and are sent to the server with every HTTP request. They are suitable for data that needs to persist for a defined period.

Advantages

Considerations


4. Using URL Parameters

Passing the dev mode status via URL parameters is less persistent but useful for testing and debugging purposes.

Implementation

Advantages

Considerations


5. Using Server-Side Sessions

Storing the dev mode status in a server-side session provides more security but requires backend implementation.

Implementation

Advantages

Considerations





Implementing a Toggle Switch with Dev-Mode Functionality

This guide provides a refined approach to integrating a toggle switch for toggling developer-specific content and managing dynamic UI elements. Key sections include HTML, updated CSS styles, and simplified JavaScript that ensures the toggle state persists across browser sessions.

  1. Add the Toggle Switch to the Page

    Replace the existing checkbox with the following HTML structure to create a toggle switch:

    <label class="switch">
      <input type="checkbox" id="devToggle">
      <span class="slider"></span>
    </label>
    <span style="margin-left: 10px;">Dev Mode</span>
    

    This transforms the checkbox into a modern toggle switch.

  2. Use Updated CSS Styles for the Toggle Switch

    Replace the CSS styles with the following concise and efficient code:

    /* Toggle Switch Styles */
    .switch {
      position: relative;
      display: inline-block;
      width: 30px;
      height: 14px;
      vertical-align: middle;
    }
    
    .switch input {
      opacity: 0;
      width: 0;
      height: 0;
    }
    
    .slider {
      position: absolute;
      cursor: pointer;
      background-color: #ccc;
      border-radius: 34px;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      transition: background-color 0.4s;
    }
    
    .slider:before {
      position: absolute;
      content: "";
      height: 10px;
      width: 10px;
      left: 2px;
      bottom: 2px;
      background-color: white;
      border-radius: 50%;
      transition: transform 0.4s;
    }
    
    /* When the checkbox is checked */
    input:checked + .slider {
      background-color: #2196F3;
    }
    
    input:checked + .slider:before {
      transform: translateX(16px);
    }
    
    /* Optional: Focus styles */
    input:focus + .slider {
      box-shadow: 0 0 2px #2196F3;
    }
    
    /* Rounded sliders */
    .slider {
      border-radius: 34px;
    }
    
    .slider:before {
      border-radius: 50%;
    }
    

    This set of styles ensures the toggle switch remains compact and visually appealing.

  3. Mark Developer-Specific Content

    Wrap developer-specific content in a container with the class dev-only to control its visibility:

    <span class="dev-only">
      <a href="./link.html" style="color: white; text-decoration: none;">Link</a>
      <!-- Other dev-only elements -->
    </span>
    

    This step ensures content visibility responds to the toggle state.

  4. Simplify JavaScript Logic

    Update the JavaScript to include a brief updateArrows function, focusing on the toggle functionality and essential arrow updates:

    <script>
      // Function to update Dev Mode and save the state to localStorage
      function updateDevMode() {
        const isDevMode = document.getElementById('devToggle').checked;
        const devElements = document.querySelectorAll('.dev-only');
        devElements.forEach(el => {
          el.style.display = isDevMode ? '' : 'none';
        });
        // Save the state to localStorage
        localStorage.setItem('devMode', isDevMode);
    
        // Show or hide arrows based on dev mode
        const arrowSVG = document.getElementById('arrow');
        if (arrowSVG) {
          arrowSVG.style.display = isDevMode ? '' : 'none';
        }
    
        // If in dev mode, calculate arrows; otherwise, skip
        if (isDevMode) {
          updateArrows();
        }
      }
    
      // Initialize the toggle state based on localStorage
      function initializeDevMode() {
        const savedDevMode = localStorage.getItem('devMode');
        const isDevMode = savedDevMode === 'true'; // Convert string to boolean
        document.getElementById('devToggle').checked = isDevMode;
        updateDevMode(); // Apply the state
      }
    
      // Add event listener for toggle switch
      document.getElementById('devToggle').addEventListener('change', updateDevMode);
    
      // Function to update arrows (simplified)
      function updateArrows() {
        // Core logic to adjust arrow visibility and positions
        const arrowLine = document.getElementById("arrow-line");
        const deviceInterface = document.getElementById("device-interface");
        const iosDevices = document.getElementById("ios-devices");
    
        if (!deviceInterface || !iosDevices) {
          if (arrowLine) arrowLine.style.display = 'none';
          return;
        }
    
        if (arrowLine) arrowLine.style.display = ''; // Ensure arrow visibility if elements exist
      }
    
      // Initialize on page load
      window.onload = initializeDevMode;
    </script>
    

    This streamlined approach focuses on visibility toggling and essential adjustments, avoiding unnecessary complexity.




Storing the Toggle State for Persistent Dev Mode

To ensure that the developer mode preference persists across browser sessions and new tabs, the toggle state is stored using the browser's localStorage. This approach allows the application to remember the user's choice even after closing and reopening the browser:

  1. Saving the Toggle State:
    • When the toggle switch is changed, the updateDevMode function saves the current state (true for dev mode enabled, false for disabled) to localStorage using localStorage.setItem('devMode', isDevMode);.
  2. Initializing the Toggle State on Page Load:
    • Upon loading the page, the initializeDevMode function retrieves the saved state from localStorage using localStorage.getItem('devMode');.
    • The retrieved value is a string, so it is compared to 'true' to convert it to a boolean.
    • The toggle switch is then set to the retrieved state, and updateDevMode is called to apply the visibility settings.
  3. Default State When localStorage Is Cleared:
    • If localStorage is cleared (e.g., by deleting the cache), there will be no saved state.
    • In this case, the toggle switch defaults to not dev mode, as the checked attribute is removed from the HTML input.
    • The initializeDevMode function sets the toggle to false (not dev mode) when no saved state is found.
<script>
  // Function to update Dev Mode and save the state to localStorage
  function updateDevMode() {
    const isDevMode = document.getElementById('devToggle').checked;
    const devElements = document.querySelectorAll('.dev-only');
    devElements.forEach(el => {
      el.style.display = isDevMode ? '' : 'none';
    });
    // Save the state to localStorage
    localStorage.setItem('devMode', isDevMode);

    // Show or hide arrows based on dev mode
    const arrowSVG = document.getElementById('arrow');
    if (arrowSVG) {
      arrowSVG.style.display = isDevMode ? '' : 'none';
    }

    // If in dev mode, calculate arrows; otherwise, skip
    if (isDevMode) {
      updateArrows();
    }
  }

  // Initialize the toggle state based on localStorage
  function initializeDevMode() {
    const savedDevMode = localStorage.getItem('devMode');
    const isDevMode = savedDevMode === 'true'; // Convert string to boolean
    document.getElementById('devToggle').checked = isDevMode;
    updateDevMode(); // Apply the state
  }

  // Add event listener for toggle switch
  document.getElementById('devToggle').addEventListener('change', updateDevMode);

  // Function to update arrows (simplified)
  function updateArrows() {
    // Core logic to adjust arrow visibility and positions
    const arrowLine = document.getElementById("arrow-line");
    const deviceInterface = document.getElementById("device-interface");
    const iosDevices = document.getElementById("ios-devices");

    if (!deviceInterface || !iosDevices) {
      if (arrowLine) arrowLine.style.display = 'none';
      return;
    }

    if (arrowLine) arrowLine.style.display = ''; // Ensure arrow visibility if elements exist
  }

  // Initialize on page load
  window.onload = initializeDevMode;
</script>

Behavior on Cache Reset

Default State: By removing the checked attribute from the HTML checkbox, the default state when localStorage is cleared is not dev mode.

Persistence: When the browser cache is not cleared, the toggle state persists across sessions and new tabs, reflecting the user's last choice.

Initialization Logic Correction

  1. Incorrect: const isDevMode = savedDevMode === 'false';
  2. Corrected: const isDevMode = savedDevMode === 'true';

This correction ensures that when localStorage contains 'true', isDevMode is set to true, enabling dev mode. Conversely, if localStorage contains 'false' or is null, isDevMode is set to false, disabling dev mode.




Resolving Asynchronous Loading Issues in Web Applications Using jQuery and Local Storage

Understanding the Problem

When utilizing jQuery's .load() method to inject external HTML content, such as link.html, into a web page, inconsistencies may arise. This typically occurs because scripts that interact with elements within the loaded content execute before those elements are fully integrated into the Document Object Model (DOM). As a result, elements like devToggle might not be recognized, leading to unpredictable behavior.

Solution Overview

To address the aforementioned issue, the following steps are recommended:

  1. Ensure jQuery is Loaded Before Use: The jQuery library must be included prior to any scripts that depend on it.
  2. Utilize the .load() Callback Function: Incorporate initialization and event listener attachment within the callback function of the .load() method to ensure elements are available in the DOM.
  3. Adjust JavaScript Functions: Modify initialization and event listener functions to operate after the content has been loaded.
  4. Ensure All Dependent Code Runs After Loading: Any scripts that interact with dynamically loaded content should execute only after those elements are fully loaded.

Implementation Steps

1. Include jQuery Before Using It

It is imperative to include the jQuery library before any scripts that rely on it. This ensures that jQuery functions are available when needed.

<!-- Include jQuery first -->
<script src="jquery-3.6.0.min.js"></script>

2. Use the .load() Callback Function

Modify the .load() method to include a callback function. This callback executes after the external content has been successfully loaded, ensuring that the elements are present in the DOM before any interactions occur.

$(function() {
    $("#common-html").load("link.html", function() {
        // Initialization code goes here
        initializeDevMode();

        // Add event listener for the toggle switch
        const devToggle = document.getElementById('devToggle');
        if (devToggle) {
            devToggle.addEventListener('change', updateDevMode);
        } else {
            console.error('devToggle element not found.');
        }
    });
});

3. Adjust JavaScript Functions

Remove any event listeners or initialization calls that occur before the content is loaded. Ensure that functions like initializeDevMode() and updateDevMode() are defined and accessible.

// Define the updateDevMode function
function updateDevMode() {
    const isDevMode = document.getElementById('devToggle').checked;
    console.log('Dev Mode toggled:', isDevMode);
    const devElements = document.querySelectorAll('.dev-only');
    devElements.forEach(el => {
        el.style.display = isDevMode ? '' : 'none';
    });
    // Save the state to localStorage
    localStorage.setItem('devMode', isDevMode);

    // Show or hide arrows based on dev mode
    const arrowSVG = document.getElementById('arrow');
    if (arrowSVG) {
        arrowSVG.style.display = isDevMode ? '' : 'none';
    }

    // If in dev mode, calculate arrows; otherwise, skip
    if (isDevMode) {
        updateArrows();
    }
}

// Define the initializeDevMode function
function initializeDevMode() {
    const savedDevMode = localStorage.getItem('devMode');
    const isDevMode = savedDevMode === 'true';
    console.log('Initializing Dev Mode:', isDevMode);
    const devToggle = document.getElementById('devToggle');
    if (devToggle) {
        devToggle.checked = isDevMode;
        updateDevMode();
    } else {
        console.error('devToggle element not found during initialization.');
    }
}

// Define the updateArrows function
function updateArrows() {
    // Arrow updating logic...
}



Dynamically Updating Content Based on Dev Mode Toggle in JavaScript

In web development, adjusting the user interface based on specific modes or settings, such as a development mode (dev mode), is a common practice. This enhances user experience by providing additional information or functionality when necessary. The following outlines two methods to dynamically update webpage content to display "Echo" when not in dev mode and "Echocardiography" when in dev mode. Both methods utilize JavaScript to manipulate DOM elements based on the state of a dev mode toggle.


Method I: Updating Text Content Using JavaScript

HTML Update

Begin by ensuring the HTML structure includes an element with an identifiable id for the link text:

<td align="center" style="background-color: rgba(224, 223, 248, 0.3);">
  <span id="echo-devices">
    <b><a href="./echocardiography.html" id="echo-link">Echocardiography</a></b>
  </span>
</td>

In this snippet, the id="echo-link" attribute is added to the <a> tag, allowing for direct manipulation of its text content.

JavaScript Update

Within the updateDevMode function, add logic to update the text content of the link based on the dev mode state:

function updateDevMode() {
  const isDevMode = document.getElementById('devToggle').checked;
  console.log('Dev Mode toggled:', isDevMode);

  const devElements = document.querySelectorAll('.dev-only');
  devElements.forEach(el => {
    el.style.display = isDevMode ? '' : 'none';
  });

  // Update Echo text based on dev mode
  const echoLink = document.getElementById('echo-link');
  echoLink.textContent = isDevMode ? "Echocardiography" : "Echo";

  // Save the state to localStorage
  localStorage.setItem('devMode', isDevMode);

  // Show or hide arrows based on dev mode
  const arrowSVG = document.getElementById('arrow');
  if (arrowSVG) {
    arrowSVG.style.display = isDevMode ? '' : 'none';
  }

  // If in dev mode, calculate arrows; otherwise, skip
  if (isDevMode) {
    updateArrows();
  }
}

Explanation

Behavior

  1. Dev Mode Enabled: The link displays as Echocardiography.
  2. Dev Mode Disabled: The link displays as Echo.

This approach provides a seamless user experience by dynamically adjusting the link text with minimal impact on the existing codebase.


Method II: Modifying Inner HTML to Include Conditional Line Break

HTML Structure

The HTML remains the same as in Method I:

<td align="center" style="background-color: rgba(224, 223, 248, 0.3);">
  <span id="echo-devices">
    <b><a href="./echocardiography.html">Echocardiography</a></b>
  </span>
</td>

JavaScript Update

Modify the updateDevMode function to adjust the inner HTML of the echo-devices element:

function updateDevMode() {
  const isDevMode = document.getElementById('devToggle').checked;
  console.log('Dev Mode toggled:', isDevMode);

  const devElements = document.querySelectorAll('.dev-only');
  devElements.forEach(el => {
    el.style.display = isDevMode ? '' : 'none';
  });

  // Update Echo text and line break for dev mode
  const echoDevices = document.getElementById('echo-devices');
  if (isDevMode) {
    echoDevices.innerHTML = '<b><a href="./echocardiography.html">Echocardiography</a></b><br>';
  } else {
    echoDevices.innerHTML = '<b><a href="./echocardiography.html">Echo</a></b>';
  }

  // Save the state to localStorage
  localStorage.setItem('devMode', isDevMode);

  // Show or hide arrows based on dev mode
  const arrowSVG = document.getElementById('arrow');
  if (arrowSVG) {
    arrowSVG.style.display = isDevMode ? '' : 'none';
  }

  // If in dev mode, calculate arrows; otherwise, skip
  if (isDevMode) {
    updateArrows();
  }
}

Explanation

Behavior

  1. Dev Mode Enabled:
    <b><a href="./echocardiography.html">Echocardiography</a></b><br>
  2. Dev Mode Disabled:
    <b><a href="./echocardiography.html">Echo</a></b>

This method provides more control over the HTML content, allowing for structural changes such as adding or removing line breaks in addition to changing the text.





Python Script


Converting HTML Tables to Excel with Python

Automating the conversion of HTML tables to Excel spreadsheets can streamline data analysis and reporting tasks. This guide demonstrates how to use Python's pandas library to parse HTML content, transform it into a DataFrame, and export it as an Excel file on the user's Desktop, ensuring compatibility across different operating systems.


Python Script:

Below is the refined Python script tailored to perform the HTML to Excel conversion. The HTML content is represented by a placeholder (~~~) for brevity.

import pandas as pd
from pathlib import Path
import os

# The HTML content as a string
html_content = """
~~~
"""

def get_desktop_path():
    """
    Returns the path to the user's Desktop in a cross-platform manner.
    """
    home = Path.home()
    desktop = home / 'Desktop'
    
    if desktop.exists() and desktop.is_dir():
        return desktop
    else:
        # Handle cases where Desktop might be localized or missing
        # Attempt to retrieve from environment variables
        if os.name == 'nt':  # For Windows
            desktop = Path(os.path.join(os.environ.get('USERPROFILE'), 'Desktop'))
        else:  # For macOS and Linux
            desktop = Path(os.path.join(os.environ.get('HOME'), 'Desktop'))
        
        if desktop.exists() and desktop.is_dir():
            return desktop
        else:
            # As a fallback, use the home directory
            return home

def main():
    try:
        # Read the HTML into pandas
        df_list = pd.read_html(html_content)  # This returns a list of DataFrames
        if not df_list:
            print("No tables found in the provided HTML content.")
            return
        df = df_list[0]  # Assuming there's only one table, it's the first item
        
        # Get the Desktop path
        desktop_path = get_desktop_path()
        
        # Define the output file path
        output_file = desktop_path / "converted_table.xlsx"
        
        # Export to Excel
        df.to_excel(output_file, index=False)
        
        print(f"Excel file successfully saved to: {output_file}")
    
    except Exception as e:
        print(f"An error occurred: {e}")

if __name__ == "__main__":
    main()

Written on November 12th, 2024


Embedded Scripting Interface

An Embedded Scripting Interface is a component within a software application that allows users to write and execute custom scripts in real-time using a programming language such as Python or R. This interface provides the flexibility to interact with the application’s internal functionality, perform complex computations, or extend features without modifying the core code. By integrating interpreters like Python or R, users can leverage powerful scripting capabilities, making the software highly customizable and adaptable to various use cases such as data analysis, automation, or integration with other tools.


R Packages

Installation of rpy2

If the rpy2 package is not yet available, it can be installed via pip using the following command:

pip install rpy2

Utilizing R within Python using rpy2

Once the rpy2 package is installed, R packages and functions can be accessed from Python. The following example demonstrates how to achieve this:

# Import rpy2's interface to R
import rpy2.robjects as ro
from rpy2.robjects.packages import importr
from rpy2.robjects.vectors import FloatVector

# Import the base R package
base = importr('base')

# Example of using a simple R function, such as the sum function
r_sum = ro.r['sum']  # Access R's sum function
result = r_sum(FloatVector([1, 2, 3, 4, 5]))

print(f"Sum calculated using R: {result[0]}")

# Example of installing and using a specific R package
utils = importr('utils')
utils.install_packages('ggplot2')  # Install an R package like ggplot2  

Executing R Scripts from Python

In instances where a more complex R script is required, the subprocess module can be utilized to execute entire R scripts from within Python:

import subprocess

# Running an R script from Python
subprocess.run(['Rscript', 'your_script.R'])  

This approach facilitates seamless interaction with R functions and packages within a Python environment, enabling more versatile workflows that combine the strengths of both languages.


Python Interpreter

To enable interaction with a Python programming interface within standalone software, an embedded Python interpreter can be integrated. This allows the execution of Python code within the application's environment. Several approaches can be employed based on the requirements:

1. Using the code Module (Interactive Console):

Python provides a built-in module called code that can be used to embed an interactive interpreter session within an application. This method is suitable for both GUI and CLI applications. For instance, by invoking the InteractiveConsole class, users can input Python code, which is executed in real-time within the program's environment. This method facilitates straightforward access to Python's functionality.

import code

# Open an interactive console for user input
def start_interpreter():
   console = code.InteractiveConsole(locals=globals())
   console.interact("Welcome to the Python Interactive Console within your software.")

start_interpreter()

2. Using exec() for Inline Code Execution:

Another option is to allow dynamic execution of Python code using the exec() function. This method enables users to input code as a string, which is then executed in the program's context. It is a flexible and dynamic approach for integrating Python scripting capabilities.

def execute_user_code():
   user_code = input("Enter Python code: ")
   try:
      exec(user_code, globals())  # Execute in the global context
   except Exception as e:
      print(f"Error executing code: {e}")

execute_user_code()

3. Building a Full IDE or Editor:

For a more sophisticated experience, a complete IDE or code editor can be embedded using GUI frameworks like Tkinter or PyQt/PySide. These frameworks allow the creation of text editors that provide a user-friendly interface for writing and executing Python code.

import tkinter as tk
from tkinter import scrolledtext

def execute_code():
   code_to_run = editor.get("1.0", tk.END)
   try:
      exec(code_to_run)
   except Exception as e:
      output_box.insert(tk.END, f"Error: {e}\n")

root = tk.Tk()
root.title("Python IDE")

editor = scrolledtext.ScrolledText(root, wrap=tk.WORD, width=60, height=20)
editor.pack()

run_button = tk.Button(root, text="Run Code", command=execute_code)
run_button.pack()

output_box = scrolledtext.ScrolledText(root, wrap=tk.WORD, width=60, height=10)
output_box.pack()

root.mainloop()

4. Embedding IPython or Jupyter Kernel:

For a more powerful interactive environment, IPython or a Jupyter kernel can be embedded. IPython offers an enhanced interactive shell that can be integrated into the application. Additionally, running a Jupyter kernel in the background allows for a richer interface, which can connect to the application for advanced coding and data manipulation.

from IPython import embed

# Start an IPython session within the software
embed()



Installing `rpy2` in PyCharm on macOS

This guide provides detailed steps for installing and configuring the `rpy2` Python package within PyCharm on macOS, with special consideration given to different R installation methods. For installations where R has been installed via a `.pkg` file rather than Homebrew, specific path configurations may be required. Additionally, guidance is included for installing R via Homebrew, which may streamline the setup process for `rpy2`.

Step 1: Activate the Virtual Environment in PyCharm

  1. Open the Terminal application on macOS.
  2. Activate the project-specific virtual environment by executing the following command:
    source /Users/frank/PycharmProjects/tmpPy/.venv/bin/activate
    This step establishes the correct environment for the `rpy2` installation within PyCharm.

Step 2: Execute the Corrected Command for Installing `rpy2`

Given that PyCharm’s installation path includes a space in “PyCharm CE,” an escape character (`\`) is required to prevent command errors. To install `rpy2` using PyCharm’s packaging tool:

  1. In the terminal, enter the following command:
    /Users/frank/PycharmProjects/tmpPy/.venv/bin/python /Applications/PyCharm\ CE.app/Contents/plugins/python-ce/helpers/packaging_tool.py install rpy2
    This command directly installs `rpy2` into the virtual environment by utilizing PyCharm’s internal package installer.

Step 3: Verify the Installation in PyCharm

  1. In PyCharm, navigate to View > Tool Windows > Python Packages.
  2. Search for `rpy2` in the available packages list to confirm its presence within the environment.

Additional Configuration for R Installed via .pkg Installer

If R has been installed using a `.pkg` file rather than Homebrew, the system may not automatically recognize the R installation path. To address this:

Determine the R Installation Path

  1. To locate the R installation path, enter the following command in the terminal:
    which R
    This command outputs the path to the R executable, typically /Library/Frameworks/R.framework/Resources for `.pkg` installations.

Set the R_HOME Environment Variable

  1. Set the R_HOME environment variable to the installation path by adding the following line to your shell profile file (~/.zshrc for Zsh or ~/.bash_profile for Bash) to make it persistent:
    export R_HOME=/Library/Frameworks/R.framework/Resources
  2. After adding this line, reload the terminal profile with:
    source ~/.zshrc  
    # or
    source ~/.bash_profile  

This configuration ensures `rpy2` can locate the R libraries when using an R version installed via `.pkg`.


Optional: Installing R Using Homebrew

For those who prefer to install R via Homebrew, which may simplify `rpy2` configuration:

  1. Install Homebrew if it is not already installed:
    /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
  2. Install R with Homebrew:
    brew install r
    This command installs R in a standard Homebrew location, typically /usr/local/Cellar/r/<version>, and sets up necessary environment variables automatically.
  3. Set R_HOME (if required):
    • After installation, the R path can be found by running:
      brew --prefix r
    • If additional configuration is needed, add the output path from the above command as the R_HOME value in the shell profile.

Using R Packages in Python with rpy2

A. Loading R Functions from a String or File

Example: R Function Definition

Suppose you have an R function named myFunc that processes some data:

myFunc <- function(x) {
  return(x * 2)
}

Using SignatureTranslatedAnonymousPackage

  1. Creating an R package from an R code string:
from rpy2.robjects.packages import SignatureTranslatedAnonymousPackage
from rpy2 import robjects

# R code as a string
r_code = """
myFunc <- function(x) {
    return(x * 2)
}
"""

# Create a temporary R package from the string
my_func_package = SignatureTranslatedAnonymousPackage(r_code, "myFuncPackage")
  1. Using the loaded R function:
# Call the R function from Python
result = my_func_package.myFunc(10)  # Pass data to R function
print("The result is:", result[0])  # Convert the R result to Python type for display

Here, myFunc is an attribute of my_func_package, allowing you to invoke the R function with Python data.

Using SignatureTranslatedPackage

If you have an R script in a file, for example myRFunctions.R, containing your R functions, you can load it as:

from rpy2.robjects.packages import SignatureTranslatedAnonymousPackage

with open("myRFunctions.R", "r") as r_file:
    r_code_from_file = r_file.read()

# Create an R package from the file's content
my_functions_package = SignatureTranslatedAnonymousPackage(r_code_from_file, "myFunctionsPackage")

Then call the functions from this package as shown above.

Data Conversion


B. Using R Functions from External Packages

Step 1: Import R Packages

import rpy2.robjects.packages as rpackages

# Import R's "utils" package to install packages
utils = rpackages.importr('utils')
utils.chooseCRANmirror(ind=1)  # Select a CRAN mirror

# Install a needed R package from CRAN if not already installed
package_name = 'fastICA'
if not rpackages.isinstalled(package_name):
    utils.install_packages(package_name)

Step 2: Import the R Package as a Python Object

fastica_pkg = rpackages.importr('fastICA')

Step 3: Use Functions from the Imported Package

# Example: Using the fastICA function from the fastICA package
import numpy as np

# Prepare input data (converted to R-compatible format if needed)
data = np.random.rand(100, 2)  # 100 observations, 2 variables

# Convert numpy array to R matrix
data_r = robjects.r.matrix(data, nrow=data.shape[0], ncol=data.shape[1])

# Call the fastICA function from the fastICA R package
ica_result = fastica_pkg.fastICA(data_r, n_comp=2)

This approach allows you to use any R package and its functions similarly.


C. Using R Scripts and Packages Generally

1. Loading an Entire R Script

If you have an R script, myRScript.R, containing multiple function definitions or code lines:

with open("myRScript.R", "r") as file:
    r_script = file.read()

# Convert the R script into a Python-callable form
my_r_package = SignatureTranslatedAnonymousPackage(r_script, "myRPackage")

# Now you can call the R functions defined in 'myRScript.R'
result = my_r_package.someFunction(some_args)

Ensure that someFunction is defined in myRScript.R.

2. Handling Data Exchange


D. Another Example: The Provided fastICA R Script

Using the exact steps above:

  1. Load the R script: Wrap the R code for fastICA into a string in Python or read it from a .R file.
  2. Create the R package: Using SignatureTranslatedAnonymousPackage.
  3. Use the function: Once loaded, the fastICA function becomes accessible through the created package object, where you can pass data arrays from Python (converted to R matrices) and retrieve results.

Integrating R's fastICA with Python using rpy2 on Windows

The error encountered:

ValueError: The system "%s" is not supported.

indicates that rpy2 is unable to recognize the operating system correctly. This issue often arises when rpy2 cannot locate the R installation on a Windows system or when there is a mismatch between the architectures of Python and R (e.g., 64-bit vs. 32-bit).

To resolve this issue and successfully integrate R's fastICA into a Python script using rpy2 on Windows, the following detailed steps should be followed:

1. Ensure R is Installed on the Windows System

Before using rpy2, it is necessary to have R installed on the machine.

Steps:

  1. Download R:

  2. Install R:

    • Run the downloaded installer.
    • Follow the installation prompts, accepting default settings unless specific configurations are required.
    • Note the installation directory (commonly C:\Program Files\R\R-4.3.0).

2. Set Environment Variables for R

rpy2 relies on environment variables to locate the R installation. Setting R_HOME and updating the PATH variable is essential.

Steps:

  1. Find R Installation Path:

    • Typically, R is installed in C:\Program Files\R\R-x.x.x (e.g., C:\Program Files\R\R-4.3.0).
  2. Set R_HOME Environment Variable:

    • Access Environment Variables:
      • Right-click on This PC or My Computer on the desktop or in File Explorer.
      • Select Properties.
      • Click on Advanced system settings.
      • In the System Properties window, click on the Environment Variables button.
    • Create R_HOME:
      • Under System variables, click New.
      • Set Variable name to R_HOME.
      • Set Variable value to the path of the R installation (e.g., C:\Program Files\R\R-4.3.0).
      • Click OK.
  3. Add R's bin Directory to PATH:

    • Still in the Environment Variables window:
    • Under System variables, find and select the Path variable, then click Edit.
    • Click New and add the path to R's bin directory (e.g., C:\Program Files\R\R-4.3.0\bin).
    • Click OK to save changes.
  4. Verify Environment Variables:

    • Open Command Prompt and run:
    • echo %R_HOME%

      This should display the path to the R installation.

    • Test if R is accessible by running:
    • R --version

      This should display the R version information.

3. Ensure Python and R Architectures Match

It is crucial that both Python and R are either 64-bit or 32-bit. Mismatched architectures can lead to compatibility issues.

Steps:

  1. Check Python Architecture:

    • Open Python interpreter and run:
    • import platform
      print(platform.architecture())

      The output should be similar to ('64bit', 'WindowsPE').

  2. Check R Architecture:

    • Open R GUI or RStudio and run:
    • .Machine$sizeof.pointer

      The output should be 8 for 64-bit or 4 for 32-bit.

  3. Reinstall if Necessary:

    • If there is a mismatch, reinstall Python or R to match architectures.

4. Install or Upgrade rpy2

Ensuring that rpy2 is installed and compatible with the R version is necessary.

Steps:

  1. Upgrade pip:

    pip install --upgrade pip
  2. Install rpy2:

    pip install --upgrade rpy2

    Note: Ensure that rpy2 is installed in the same Python environment used for the project (e.g., a virtual environment).

  3. Verify rpy2 Installation:

    In Python, run:

    import rpy2.robjects as robjects
    print(robjects.r('version'))

    This should display R's version information without errors.

Troubleshooting:

If errors are encountered, ensure that R is correctly installed and that the environment variables are properly set.

5. Test rpy2 Integration with R

Before integrating the fastICA function, verifying that rpy2 can interact with R is essential.

Steps:

  1. Simple Test:

    Create a Python script (e.g., test_rpy2.py) with the following content:

    import rpy2.robjects as robjects
    
    # Print R version
    print(robjects.r('version'))
    
    # Execute a simple R command
    r_sum = robjects.r('sum(c(1, 2, 3, 4, 5))')
    print("Sum from R:", r_sum[0])
  2. Run the Script:

    python test_rpy2.py
  3. Expected Output:

    • R version details.
    • Sum from R: 15
  4. If Errors Occur:

    Revisit the previous steps to ensure R is correctly installed and environment variables are set.

6. Integrate fastICA R Function into Python

Proceed to integrate the fastICA R function into the Python script.

Complete Python Script with R fastICA Integration:

import os
import numpy as np
import matplotlib.pyplot as plt
from scipy.io import wavfile
from rpy2 import robjects
from rpy2.robjects import numpy2ri
from rpy2.robjects.packages import SignatureTranslatedAnonymousPackage

# Enable the conversion between numpy arrays and R vectors/matrices
numpy2ri.activate()

# Set the maximum vector size in R to attempt to prevent memory limit issues
os.environ['R_MAX_VSIZE'] = '32GB'  # Adjust based on available system memory

# Load the fastICA.R script content
fastICA_r_script = """
fastICA <-
function (X, n.comp, alg.typ = c("parallel","deflation"),
          fun = c("logcosh", "exp"),
          alpha = 1, method = c("R", "C"),
          row.norm = FALSE, maxit = 200, tol = 1e-04,
          verbose = FALSE, w.init=NULL)
{
    dd <- dim(X)
    d <- dd[dd != 1L]
    if (length(d) != 2L)
        stop("data must be matrix-conformal")
    X <- if (length(d) != length(dd)) matrix(X, d[1L], d[2L])
    else as.matrix(X)

    if (alpha < 1 || alpha > 2)
        stop("alpha must be in range [1,2]")
    method <- match.arg(method)
    alg.typ <- match.arg(alg.typ)
    fun <- match.arg(fun)
    n <- nrow(X)
    p <- ncol(X)

    if (n.comp > min(n, p)) {
        message("'n.comp' is too large: reset to ", min(n, p))
        n.comp <- min(n, p)
    }
    if(is.null(w.init))
        w.init <- matrix(rnorm(n.comp^2),n.comp,n.comp)
    else {
        if(!is.matrix(w.init) || length(w.init) != (n.comp^2))
            stop("w.init is not a matrix or is the wrong size")
    }
    if (method == "R") {
        if (verbose) message("Centering")

        X <- scale(X, scale = FALSE)

        X <- if (row.norm) t(scale(X, scale=row.norm)) else t(X)

        if (verbose) message("Whitening")
        V <- X %*% t(X)/n

        s <- La.svd(V)
        D <- diag(c(1/sqrt(s$d)))

        K <- D %*% t(s$u)
        K <- matrix(K[1:n.comp, ], n.comp, p)
        X1 <- K %*% X
        a <- if (alg.typ == "deflation")
            ica.R.def(X1, n.comp, tol = tol, fun = fun,
                      alpha = alpha, maxit = maxit, verbose = verbose, w.init = w.init)
        else if (alg.typ == "parallel")
            ica.R.par(X1, n.comp, tol = tol, fun = fun,
                      alpha = alpha, maxit = maxit, verbose = verbose, w.init = w.init)
        w <- a %*% K
        S <- w %*% X
        A <- t(w) %*% solve(w %*% t(w))
        return(list(X = t(X), K = t(K), W = t(a), A = t(A), S = t(S)))
    } else if (method == "C") {
        a <- .C(icainc_JM,
                as.double(X),
                as.double(w.init),
                as.integer(p),
                as.integer(n),
                as.integer(n.comp),
                as.double(alpha),
                as.integer(1),
                as.integer(row.norm),
                as.integer(1L + (fun == "exp")),
                as.integer(maxit),
                as.double(tol),
                as.integer(alg.typ != "parallel"),
                as.integer(verbose),
                X = double(p * n),
                K = double(n.comp * p),
                W = double(n.comp * n.comp),
                A = double(p * n.comp),
                S = double(n.comp * n))
        X1 <- matrix(a$X, n, p)
        K <- matrix(a$K, p, n.comp)
        W <- matrix(a$W, n.comp, n.comp)
        A <- matrix(a$A, n.comp, p)
        S <- matrix(a$S, n, n.comp)
        list(X = X1, K = K, W = W, A = A, S = S)
    }
}

ica.R.def <-
    function (X, n.comp, tol, fun, alpha, maxit, verbose, w.init)
{
    if (verbose && fun == "logcosh")
        message("Deflation FastICA using logcosh approx. to neg-entropy function")
    if (verbose && fun =="exp")
        message("Deflation FastICA using exponential approx. to neg-entropy function")
    p <- ncol(X)
    W <- matrix(0, n.comp, n.comp)
    for (i in 1:n.comp) {
        if (verbose) message("Component ", i)
        w <- matrix(w.init[i,], n.comp, 1)
        if (i > 1) {
            t <- w
            t[1:length(t)] <- 0
            for (u in 1:(i - 1)) {
                k <- sum(w * W[u, ])
                t <- t + k * W[u, ]
            }
            w <- w - t
        }
        w <- w/sqrt(sum(w^2))
        lim <- rep(1000, maxit)
        it <- 1
        if (fun == "logcosh") {
            while (lim[it] > tol && it < maxit) {
                wx <- t(w) %*% X
                gwx <- tanh(alpha * wx)
                gwx <- matrix(gwx, n.comp, p, byrow = TRUE)
                xgwx <- X * gwx
                v1 <- apply(xgwx, 1, FUN = mean)
                g.wx <- alpha * (1 - (tanh(alpha * wx))^2)
                v2 <- mean(g.wx) * w
                w1 <- v1 - v2
                w1 <- matrix(w1, n.comp, 1)
                it <- it + 1
                if (i > 1) {
                    t <- w1
                    t[1:length(t)] <- 0
                    for (u in 1:(i - 1)) {
                        k <- sum(w1 * W[u, ])
                        t <- t + k * W[u, ]
                    }
                    w1 <- w1 - t
                }
                w1 <- w1/sqrt(sum(w1^2))
                lim[it] <- Mod(Mod(sum((w1 * w))) - 1)
                if (verbose)
                    message("Iteration ", it - 1, " tol = ", format(lim[it]))
                w <- matrix(w1, n.comp, 1)
            }
        }
        if (fun == "exp") {
            while (lim[it] > tol && it < maxit) {
                wx <- t(w) %*% X
                gwx <- wx * exp(-(wx^2)/2)
                gwx <- matrix(gwx, n.comp, p, byrow = TRUE)
                xgwx <- X * gwx
                v1 <- apply(xgwx, 1, FUN = mean)
                g.wx <- (1 - wx^2) * exp(-(wx^2)/2)
                v2 <- mean(g.wx) * w
                w1 <- v1 - v2
                w1 <- matrix(w1, n.comp, 1)
                it <- it + 1
                if (i > 1) {
                    t <- w1
                    t[1:length(t)] <- 0
                    for (u in 1:(i - 1)) {
                        k <- sum(w1 * W[u, ])
                        t <- t + k * W[u, ]
                    }
                    w1 <- w1 - t
                }
                w1 <- w1/sqrt(sum(w1^2))
                lim[it] <- Mod(Mod(sum((w1 * w))) - 1)
                if (verbose)
                    message("Iteration ", it - 1, " tol = ", format(lim[it]))
                w <- matrix(w1, n.comp, 1)
            }
        }
        W[i, ] <- w
    }
    W
}

ica.R.par <- function (X, n.comp, tol, fun, alpha, maxit, verbose, w.init)
{
    Diag <- function(d) if(length(d) > 1L) diag(d) else as.matrix(d)
    p <- ncol(X)
    W <- w.init
    sW <- La.svd(W)
    W <- sW$u %*% Diag(1/sW$d) %*% t(sW$u) %*% W
    W1 <- W
    lim <- rep(1000, maxit)
    it <- 1
    if (fun == "logcosh") {
        if (verbose)
            message("Symmetric FastICA using logcosh approx. to neg-entropy function")
        while (lim[it] > tol && it < maxit) {
            wx <- W %*% X
            gwx <- tanh(alpha * wx)
            v1 <- gwx %*% t(X)/p
            g.wx <- alpha * (1 - (gwx)^2)
            v2 <- Diag(apply(g.wx, 1, FUN = mean)) %*% W
            W1 <- v1 - v2
            sW1 <- La.svd(W1)
            W1 <- sW1$u %*% Diag(1/sW1$d) %*% t(sW1$u) %*% W1
            lim[it + 1] <- max(Mod(Mod(diag(W1 %*% t(W))) - 1))
            W <- W1
            if (verbose)
                message("Iteration ", it, " tol = ", format(lim[it + 1]))
            it <- it + 1
        }
    }
    if (fun == "exp") {
        if (verbose)
            message("Symmetric FastICA using exponential approx. to neg-entropy function")
        while (lim[it] > tol && it < maxit) {
            wx <- W %*% X
            gwx <- wx * exp(-(wx^2)/2)
            v1 <- gwx %*% t(X)/p
            g.wx <- (1 - wx^2) * exp(-(wx^2)/2)
            v2 <- Diag(apply(g.wx, 1, FUN = mean)) %*% W
            W1 <- v1 - v2
            sW1 <- La.svd(W1)
            W1 <- sW1$u %*% Diag(1/sW1$d) %*% t(sW1$u) %*% W1
            lim[it + 1] <- max(Mod(Mod(diag(W1 %*% t(W))) - 1))
            W <- W1
            if (verbose)
                message("Iteration ", it, " tol = ", format(lim[it + 1]))
            it <- it + 1
        }
    }
    W
}
"""

# Create an R package with the fastICA function
fastICA_r = SignatureTranslatedAnonymousPackage(fastICA_r_script, "fastICA")

# Define function to read and normalize .wav audio files using only the first few seconds
def fetch_wav_and_convert_to_numpy(file_path, duration_seconds=1):
    sample_rate, audio_data = wavfile.read(file_path)
    max_samples = int(sample_rate * duration_seconds)
    audio_data = audio_data[:max_samples]  # Use only the first few seconds of the data
    # Normalize if not silent audio
    if np.max(np.abs(audio_data)) != 0:
        normalized_audio = audio_data / np.max(np.abs(audio_data))
    else:
        normalized_audio = audio_data
    return normalized_audio, sample_rate

# Function to plot waveforms using matplotlib
def plot_waveforms(signals, sample_rate, titles, colors):
    total_num_plots = len(signals)
    plt.figure(figsize=(6.5, 3 * total_num_plots), dpi=100)
    for i, (audio_array, title, color) in enumerate(zip(signals, titles, colors), start=1):
        time_axis = np.linspace(0, len(audio_array) / sample_rate, num=len(audio_array))
        plt.subplot(total_num_plots, 1, i)
        plt.plot(time_axis, audio_array, label=title, color=color)
        plt.title(title)
        plt.xlabel("Time (seconds)")
        plt.ylabel("Amplitude (normalized)")
        plt.legend()
        plt.tight_layout()
    plt.show()

# Main function to perform ICA using fastICA in R
def run_ica():
    heart_sound_path = "heart_sound.wav"
    lung_sound_path = "lung_sound.wav"

    # Fetch the first few seconds of audio for memory efficiency
    heart_sound, sr_heart = fetch_wav_and_convert_to_numpy(heart_sound_path, duration_seconds=1)
    lung_sound, sr_lung = fetch_wav_and_convert_to_numpy(lung_sound_path, duration_seconds=1)

    # Ensure both audio files have the same sample rate
    if sr_heart != sr_lung:
        raise ValueError("Sample rates do not match.")

    sr = sr_heart  # Using the common sample rate

    # Stack both signals for ICA; shape will be (2, number_of_samples)
    S = np.vstack((heart_sound, lung_sound))

    # Transpose the data to match R's expected input format (observations × variables)
    # fastICA expects data rows as observations and columns as variables/features
    S_transposed = S.T

    # Convert the numpy array to an R matrix
    S_r = robjects.r.matrix(S_transposed, nrow=S_transposed.shape[0], ncol=S_transposed.shape[1])

    # Perform ICA using the R function
    # Use underscores in argument names for Python function calls
    result = fastICA_r.fastICA(
        S_r,
        n_comp=2,  # Fixed: replaced n.comp with n_comp
        alg_typ="parallel",
        fun="logcosh",
        maxit=300,
        tol=1e-04,
        verbose=True
    )

    # Extract components from R result
    components_r = np.array(result.rx2("S"))

    # The independent components matrix S from the R result is oriented as (observations × components)
    # Transpose it back to match the shape (components × samples) for plotting
    components = components_r.T

    # Prepare data for plotting
    signals = [heart_sound, lung_sound, components[:, 0], components[:, 1]]
    titles = [
        "Original Heart Sound Waveform",
        "Original Lung Sound Waveform",
        "Separated Signal 1",
        "Separated Signal 2"
    ]
    colors = ['red', 'blue', 'purple', 'cyan']

    # Plot the waveforms
    plot_waveforms(signals, sr, titles, colors)
    print("Waveforms have been plotted successfully.\n")

# Run the ICA process
if __name__ == "__main__":
    run_ica()

Explanation:

A. Loading R Functions into Python

B. Data Preparation for fastICA

C. Executing the R fastICA Function

D. Plotting the Results

7. Detailed Explanation and Best Practices

A. Loading R Functions into Python

B. Data Preparation for fastICA

C. Executing the R fastICA Function

D. Plotting the Results

8. Troubleshooting Common Issues

A. rpy2 Cannot Find R Installation

B. Permissions Issues

C. R Package Dependencies

D. Memory Limitations Persist

9. Additional Tips

A. Using RStudio for Testing

Before integrating with Python, testing the fastICA.R script in RStudio ensures that it functions as expected with sample data.

B. Logging and Debugging

C. Validate Separated Signals

10. Final Thoughts

Integrating R functions into Python using rpy2 can be powerful but requires careful setup, especially on Windows systems. By following the steps outlined above, the ValueError should be resolved, and Independent Component Analysis (ICA) using R's fastICA within Python projects can be successfully performed.

If issues persist after following these steps, the following actions are recommended:


Troubleshooting rpy2 Script Execution in PyCharm on Windows

It is acknowledged that running the test_rpy2.py script via the terminal successfully yields the expected output:

R version details.
Sum from R: 15

However, encountering issues when executing the same script within PyCharm on a Windows system can be attributed to several factors. The following steps provide a comprehensive approach to diagnosing and resolving the problem:

1. Verify Python Interpreter Configuration in PyCharm

Steps:

  1. Open Project Settings:
    • Navigate to File > Settings (or PyCharm > Preferences on macOS).
  2. Check Python Interpreter:
    • In the Settings window, select Project: [Your Project Name] > Python Interpreter.
    • Ensure that the interpreter listed matches the one used in the terminal where the script runs successfully.
    • If multiple interpreters are available, select the appropriate one or add a new interpreter if necessary.
  3. Apply Changes:
    • After selecting the correct interpreter, click Apply and then OK to save the changes.

2. Ensure Environment Variables Are Recognized by PyCharm

PyCharm may not inherit the environment variables set in the system by default. Specifically, R_HOME and the PATH variable pointing to R's bin directory must be accessible within PyCharm.

Steps:

  1. Access Run Configurations:
    • Go to Run > Edit Configurations.
  2. Select Your Script Configuration:
    • Choose the configuration for test_rpy2.py or create a new one if it doesn't exist.
  3. Set Environment Variables:
    • In the selected configuration, locate the Environment variables section.
    • Click the ... button to open the environment variables dialog.
    • Add the following variables if they are not already present:
      • Variable Name: R_HOME
      • Variable Value: C:\Program Files\R\R-4.3.0 (replace with the actual R installation path)
    • Ensure that the R bin directory is included in the PATH variable. If not, append it:
      • Variable Name: PATH
      • Variable Value: %PATH%;C:\Program Files\R\R-4.3.0\bin (adjust the path as necessary)
  4. Apply and Save:
    • Click OK to close the environment variables dialog.
    • Click Apply and then OK to save the run configuration.

3. Confirm rpy2 Installation in the Selected Interpreter

Ensure that rpy2 is installed in the Python interpreter that PyCharm is utilizing.

Steps:

  1. Open Python Interpreter Settings:
    • Navigate to File > Settings > Project: [Your Project Name] > Python Interpreter.
  2. Check Installed Packages:
    • In the list of installed packages, verify that rpy2 is present.
    • If rpy2 is missing, install it by clicking the + button, searching for rpy2, and proceeding with the installation.

4. Test R Accessibility Within PyCharm

Create a simple test within PyCharm to verify that R is accessible through rpy2.

Steps:

  1. Create a New Python Script:
    • In the project, create a new Python file named pycharm_r_test.py.
  2. Add the Following Code:
    import rpy2.robjects as robjects
    
    # Print R version
    print(robjects.r('version'))
    
    # Execute a simple R command
    r_sum = robjects.r('sum(c(1, 2, 3, 4, 5))')
    print("Sum from R:", r_sum[0])
  3. Run the Script:
    • Right-click on pycharm_r_test.py and select Run 'pycharm_r_test'.

Expected Output:

R version details.
Sum from R: 15

If Errors Occur:

Note the error messages displayed in the Run window. Common issues may relate to environment variables, interpreter mismatches, or rpy2 installation problems.

5. Address Common Issues

A. Environment Variables Not Set Correctly

Revisit Step 2 to ensure that R_HOME and PATH are correctly configured within PyCharm's run configurations.

B. Interpreter Mismatch

Confirm that the Python interpreter used in PyCharm matches the one in the terminal where the script executes successfully.

C. Permissions Issues

Run PyCharm as an administrator to rule out permission-related problems:

D. Missing R Packages

Ensure that all necessary R packages required by rpy2 are installed. Install missing packages using R or within the Python script:

robjects.r('install.packages("packageName")')

6. Additional Troubleshooting Steps

A. Reinstall rpy2

Sometimes, reinstalling rpy2 can resolve underlying issues.

pip uninstall rpy2
pip install rpy2

B. Verify R Installation Path

Double-check the R installation path specified in R_HOME and PATH. Ensure there are no typos or incorrect directory references.

C. Check for Multiple R Installations

Having multiple R versions installed can cause conflicts. Ensure that R_HOME points to the correct and intended R version.

D. Review PyCharm Logs

PyCharm maintains logs that can provide insights into errors. Navigate to Help > Show Log in Explorer to access the logs.

7. Consider Using Python's Native ICA Implementations

If integration with R via rpy2 proves too cumbersome, alternative approaches within Python may offer smoother workflows.

Steps:

  1. Use scikit-learn's FastICA:
    • scikit-learn provides a robust implementation of FastICA.
    • Install scikit-learn if not already installed:
    • pip install scikit-learn
  2. Example Usage:
    import numpy as np
    from sklearn.decomposition import FastICA
    
    # Sample data: mixed signals
    S = np.c_[heart_sound, lung_sound].T
    
    # Initialize FastICA
    ica = FastICA(n_components=2, random_state=0)
    
    # Fit and transform the data
    S_ = ica.fit_transform(S)  # Recovered signals
    
    print("Separated Signals:")
    print(S_)

Advantages:

8. Final Recommendations

Integrating R functions into Python using rpy2 offers powerful capabilities but necessitates meticulous configuration, especially within integrated development environments like PyCharm on Windows systems. By following the outlined steps, the underlying issues hindering the execution of rpy2 within PyCharm should be effectively addressed.

For persistent challenges, consulting the following resources is advisable:


Dynamic Coordinate Scaling for Mathematical Simulation

Simulating mathematical models frequently involves plotting functions or data points within a specified x-range. However, effectively visualizing these plots within a given canvas requires dynamically adjusting the y-axis to fit the function’s output range onto the screen. Techniques employed by tools like R and other mathematical software can automatically determine appropriate y-axis scaling. The methodologies described here explain how to achieve similar adaptive scaling for both the x and y axes, ensuring that the entire model is represented proportionally on the canvas.


1. Calculating Function Value Ranges

When simulating mathematical functions, the range of y-values often needs to be determined automatically after specifying the x-range to fully display the function within the visualization area. This involves:

  1. Defining the x-axis domain (minimum and maximum x-values).
  2. Sampling the function to find corresponding y-values.
  3. Determining the minimum and maximum y-values for the given x-domain.

Once these ranges are known, a transformation can be applied that maps these data ranges to the pixel coordinates of the canvas.

Mathematical Model

Assume a function f(x) is defined over an x-range of [xMin, xMax]. The domain of the y-axis, [yMin, yMax], is determined by evaluating the function over this domain:

$$ y_{\text{values}} = \{ f(x) \mid x \in [xMin, xMax] \} $$
$$ yMin = \min(y_{\text{values}}), \quad yMax = \max(y_{\text{values}}) $$

With these ranges, a linear transformation is used to map the domain space to the canvas space:

For a canvas of width canvasWidth and height canvasHeight:

$$ \text{pixelX} = \left(\frac{x - xMin}{xMax - xMin}\right) \times \text{canvasWidth} $$
$$ \text{pixelY} = \left(1 - \frac{f(x) - yMin}{yMax - yMin}\right) \times \text{canvasHeight} $$

This transformation ensures that the function’s domain and range are mapped appropriately to the canvas coordinate system, with xMin mapping to pixel x = 0 and xMax mapping to pixel x = canvasWidth, similarly adjusting yMin and yMax along the y-axis.

// Define the function to be plotted
function f(x) {
    return Math.sin(x); // Example: a simple sine wave
}

// Define the domain of the function
const xMin = 0;
const xMax = 2 * Math.PI; // For one period of the sine wave

// Sample the function to find y-values
const samples = 1000; // Number of points to sample
let yValues = [];
for (let i = 0; i <= samples; i++) {
    const x = xMin + (i * (xMax - xMin) / samples);
    yValues.push(f(x));
}

// Calculate y-range
const yMin = Math.min(...yValues);
const yMax = Math.max(...yValues);

// Assuming a canvas context is obtained
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

function transformX(x) {
    return ((x - xMin) / (xMax - xMin)) * canvas.width;
}

function transformY(y) {
    return (1 - (y - yMin) / (yMax - yMin)) * canvas.height;
}

// Plot the function
ctx.beginPath();
for (let i = 0; i <= samples; i++) {
    const x = xMin + (i * (xMax - xMin) / samples);
    const y = f(x);
    if (i === 0) {
        ctx.moveTo(transformX(x), transformY(y));
    } else {
        ctx.lineTo(transformX(x), transformY(y));
    }
}
ctx.stroke();    

In this example, the f(x) function is a sine wave. The code samples points along the x-domain, calculates corresponding y-values, and then draws the function onto the canvas using the coordinate transformation functions transformX and transformY. This ensures that the entire sine wave is scaled and displayed appropriately across the available canvas space.


2. Automatic Scaling of Y-Axis for a Fixed X-Domain

When only the x-domain is specified, and there is uncertainty about the magnitude of the function’s output, automatic scaling methods can be implemented. These methods are not only for analyzing data, but also for plotting mathematical functions with unknown or dynamic ranges.

  1. Data-Driven Scaling: As shown above, evaluate the function over the domain, find the function’s minimum and maximum, and scale accordingly.
  2. Padding and Margins: To improve readability, padding can be added around the domain. For example, adding 10% padding ensures that the top and bottom edges of the range are not exactly at the canvas boundaries.
  3. Dynamic Adjustments: For interactive applications where the x-domain might change or when the function's behavior evolves (such as parameter changes in real-time simulations), the scaling can be recalculated dynamically to fit new function values within the canvas.

Mathematical Model for Padding

Adding padding ensures that the graph does not touch the edges of the canvas for the computed y-range. Given a padding fraction p (e.g., 0.1 for 10%):

$$ \text{padding} = p \times (yMax - yMin) $$
$$ yMin_{\text{padded}} = yMin - \text{padding} \quad \text{and} \quad yMax_{\text{padded}} = yMax + \text{padding} $$

Updating the coordinate transformation function accordingly ensures that the graph is displayed with adequate margins.

// Define padding fraction
const paddingFraction = 0.1;
const padding = paddingFraction * (yMax - yMin);
const yMinPadded = yMin - padding;
const yMaxPadded = yMax + padding;

function transformY(y) {
    return (1 - (y - yMinPadded) / (yMaxPadded - yMinPadded)) * canvas.height;
}

// Redraw the function with padded y-range
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
for (let i = 0; i <= samples; i++) {
    const x = xMin + (i * (xMax - xMin) / samples);
    const y = f(x);
    if (i === 0) {
        ctx.moveTo(transformX(x), transformY(y));
    } else {
        ctx.lineTo(transformX(x), transformY(y));
    }
}
ctx.stroke();    

By applying these transformations, the plotted function will appear centered and scaled on the canvas, ensuring a visually clear representation.


3. Comparing to Established Methods and Tools

Mathematical and statistical software packages (like R, Python’s matplotlib, or D3.js) provide built-in functionalities to automatically determine and adjust axis ranges. These tools typically perform:

In JavaScript, similar functionalities can be replicated by evaluating the function or dataset to be plotted, determining the necessary scaling, and then rendering the result on an HTML canvas or SVG using appropriate transformations as demonstrated.


4. Dynamic Updates for Real-Time Simulations

For real-time simulations or interactive applications where parameters of the mathematical model may change dynamically, the plot’s x and y axes need to be recalculated and redrawn regularly. This can be achieved by:

  1. Recalculating the function values whenever input parameters change.
  2. Updating yMin, yMax, and applying necessary transformations.
  3. Redrawing the function with the updated scaling to fit the new range onto the canvas.

This ensures that the visualization remains accurate and readable throughout simulation changes.


Dynamic Range Adjustment Techniques for Graph Visualization in JavaScript

Designing a JavaScript simulator that adaptively adjusts the x and y axes ranges can significantly improve the clarity and user experience of dynamic data visualizations. This document outlines several methodologies for setting these ranges flexibly, including mathematical models, algorithmic approaches, and example JavaScript implementations. Implementing these methods will enable the simulator to adjust automatically to data changes and user interactions while maintaining a formal, clear, and refined style.


1. Automatic Scaling Based on Data Extremes

Automatic scaling adjusts axis ranges according to the minimum and maximum values of the dataset. This involves calculating these extremes, optionally adding padding for clarity, and re-rendering the graph.

Mathematical Model:

If dataMin and dataMax represent the minimum and maximum data values along an axis, the axis range can be calculated as:

$$ \text{axisMin} = \text{dataMin} - \text{padding} \quad \text{and} \quad \text{axisMax} = \text{dataMax} + \text{padding} $$

Where padding can be a fixed value or a fraction of the range. For example, a 10% padding on the data range along the y-axis can be computed as:

$$ \text{padding} = 0.1 \times (\text{dataMax} - \text{dataMin}) $$

function autoScaleAxis(data) {
    const dataMin = Math.min(...data);
    const dataMax = Math.max(...data);
    const padding = 0.1 * (dataMax - dataMin);
    const axisMin = dataMin - padding;
    const axisMax = dataMax + padding;
    return { axisMin, axisMax };
}

// Example usage:
const yValues = [12, 15, 20, 22, 29, 35];
const { axisMin, axisMax } = autoScaleAxis(yValues);
// axisMin and axisMax can now be used to set the graph's y-axis range.    

This approach ensures the graph always displays all data points within the visible range, adjusting automatically as new data arrives or as data changes.


2. Data-Driven Adaptive Scaling with Statistical Techniques

Data-driven adaptive scaling uses statistical techniques to adjust ranges dynamically, focusing on typical data values while handling outliers effectively. For example, scaling based on specific percentiles ensures that the majority of data is visible without extreme outliers dominating the view.

Mathematical Model:

To exclude outliers, the axis range might be set between the 5th and 95th percentiles of the dataset. This can be computed by sorting the data and selecting values at these percentile ranks. If sortedData is the sorted dataset:

$$ \text{lowerBound} = \text{sortedData}[0.05 \times \text{dataLength}] $$
$$ \text{upperBound} = \text{sortedData}[0.95 \times \text{dataLength}] $$

function percentileScaleAxis(data, lowerPercentile, upperPercentile) {
    const sortedData = [...data].sort((a, b) => a - b);
    const lowerIndex = Math.floor((lowerPercentile / 100) * sortedData.length);
    const upperIndex = Math.ceil((upperPercentile / 100) * sortedData.length) - 1;
    const axisMin = sortedData[lowerIndex];
    const axisMax = sortedData[upperIndex];
    return { axisMin, axisMax };
}

// Example usage:
const yValues = [12, 15, 20, 22, 29, 35, 100, -10];
const { axisMin, axisMax } = percentileScaleAxis(yValues, 5, 95);
// This sets the y-axis range between the 5th and 95th percentiles, excluding extreme outliers.    

This method ensures that graphs remain readable even when datasets contain extreme values that would otherwise dominate the visual scale.


3. User-Interactive Zooming and Panning

Interactive zoom and pan functionality allows manual adjustment of the viewing range, providing greater flexibility and control. Libraries such as D3.js or Chart.js can be employed to implement this functionality. Programmatically, the axis range can be updated based on user inputs or interactions (e.g., mouse wheel events for zooming, click-and-drag for panning).

Mathematical Model:

Assuming an initial axis range [axisMin, axisMax], zooming can be represented as scaling this range by a zoom factor z. For zooming in:

$$ \text{newRange} = (\text{oldRange}) \times \frac{1}{z} $$

For panning horizontally or vertically by a factor p of the data range:

$$ \text{newAxisMin} = \text{oldAxisMin} + p \times (\text{axisMax} - \text{axisMin}) $$
$$ \text{newAxisMax} = \text{oldAxisMax} + p \times (\text{axisMax} - \text{axisMin}) $$

// Using D3.js for zoom and pan
const svg = d3.select('svg');
const zoom = d3.zoom()
    .scaleExtent([1, 10]) // Allows 1x to 10x zoom
    .translateExtent([[-100, -100], [width + 100, height + 100]]) // Limit panning
    .on('zoom', zoomed);

function zoomed(event) {
    // Event.transform contains scale and translation
    svg.attr('transform', event.transform);
}

// Applying zoom behavior to SVG
svg.call(zoom);    

With zoom and pan, users can focus on specific data segments or explore the graph in more detail without modifying the underlying data or redrawing the entire graph.


4. Dynamic Range Adjustments with Smoothing Intervals

Smoothing functions (e.g., moving averages or exponential smoothing) can be used to determine the axis range based on a smoothed representation of the data. This results in a more stable range over time, avoiding abrupt changes in axis scales when dealing with real-time data.

Mathematical Model:

A simple moving average for a window of size w can be applied to the data values. If data[i] represents the ith data point:

$$ \text{smoothedData}[i] = \frac{1}{w} \sum_{j=i-w+1}^{i} \text{data}[j] $$

The axis range is then computed from this smoothed data:

$$ \text{axisMin} = \min(\text{smoothedData}) \quad \text{and} \quad \text{axisMax} = \max(\text{smoothedData}) $$

function movingAverage(data, windowSize) {
    if (windowSize <= 1) return data;
    let result = [];
    for (let i = 0; i < data.length; i++) {
        let start = Math.max(0, i - windowSize + 1);
        const windowData = data.slice(start, i + 1);
        const average = windowData.reduce((sum, val) => sum + val, 0) / windowData.length;
        result.push(average);
    }
    return result;
}

// Example usage:
const yValues = [12, 15, 20, 22, 29, 35, 100, 90, 85, 80];
const smoothedYValues = movingAverage(yValues, 3);
const axisMin = Math.min(...smoothedYValues);
const axisMax = Math.max(...smoothedYValues);    

Dynamic range adjustments based on smoothed values produce more stable and visually consistent graphs, especially useful in scenarios where data points can fluctuate rapidly.


5. Real-Time Range Adjustments for Streaming Data

For data that updates continuously, the range can be adjusted in real-time based on the most recent subset of data. This involves continuously recalculating the minimum and maximum values over a rolling window of data points.

Mathematical Model:

If data is streamed in sequences and only the last n data points are considered:

$$ \text{rollingData} = \text{dataPoints}[\text{dataLength} - n, \dots, \text{dataLength} - 1] $$
$$ \text{axisMin} = \min(\text{rollingData}) \quad \text{and} \quad \text{axisMax} = \max(\text{rollingData}) $$

function realTimeScaleAxis(data, windowSize) {
    const start = Math.max(0, data.length - windowSize);
    const recentData = data.slice(start);
    const dataMin = Math.min(...recentData);
    const dataMax = Math.max(...recentData);
    return { axisMin: dataMin, axisMax: dataMax };
}

// Example usage:
let yValues = [12, 15, 20, 22, 29, 35, 28, 30, 50]; // streaming data
const windowSize = 5;
const { axisMin, axisMax } = realTimeScaleAxis(yValues, windowSize);
// axisMin and axisMax are calculated based on the last 5 data points.    

Real-time scaling ensures that graphs reflect the latest data trends while maintaining a focus on the most relevant and recent information.


6. Responsive Range Adjustment Based on Display Dimensions

Responsive scaling adjusts the range and resolution of the graph based on its display dimensions. This ensures the graph maintains clarity and proportion across various screen sizes or container dimensions.

Mathematical Model:

This approach involves mapping data ranges to pixel dimensions adaptively:

$$ \text{pixelsPerUnit} = \frac{\text{graphWidth (or height)}}{\text{axisMax} - \text{axisMin}} $$

function responsiveAxisRange(data, containerWidth, containerHeight) {
    const dataMin = Math.min(...data);
    const dataMax = Math.max(...data);
    // For simplicity, assign axes based on container aspect ratio
    const aspectRatio = containerWidth / containerHeight;
    const range = dataMax - dataMin;
    const axisMin = dataMin - 0.1 * range;
    const axisMax = dataMax + 0.1 * range;
    return { axisMin, axisMax, aspectRatio };
}

// Example usage:
const containerWidth = 800;
const containerHeight = 600;
const yValues = [12, 15, 20, 22, 29, 35];
const { axisMin, axisMax, aspectRatio } = responsiveAxisRange(yValues, containerWidth, containerHeight);
// The graph can now be drawn proportionally within these dimensions.    

Responsive range adjustments maintain visual clarity and aesthetic consistency, regardless of the platform or device dimensions.


7. Algorithmic Scaling Techniques for Advanced Control

Advanced algorithmic scaling techniques use additional computations to determine the optimal range, such as outlier detection, data clustering, or binning. These methods ensure that the graph represents the most relevant data patterns while managing outliers or large datasets efficiently.

Mathematical Model:

An example of outlier detection using the Z-score method:

$$ Z = \frac{x - \mu}{\sigma} $$

Where μ is the mean and σ is the standard deviation of the data. Data points with |Z| greater than a chosen threshold (commonly 3) are considered outliers and are excluded from the range calculation.

function removeOutliers(data, threshold = 3) {
    const mean = data.reduce((a, b) => a + b, 0) / data.length;
    const std = Math.sqrt(data.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / data.length);
    return data.filter(value => Math.abs((value - mean) / std) < threshold);
}

// Example usage:
const yValues = [12, 15, 20, 22, 29, 35, 100, -50];
const filteredData = removeOutliers(yValues, 3);
const axisMin = Math.min(...filteredData);
const axisMax = Math.max(...filteredData);    

Algorithmic scaling techniques maintain the readability and accuracy of the graph by focusing on data distributions and effectively handling outliers.


8. AI or Machine Learning-Based Range Prediction

AI or machine learning algorithms can be used to predict future data trends and adjust axis ranges proactively. This approach is beneficial for graphs visualizing highly fluctuating data, where adaptive predictive adjustments can enhance the user experience.

Mathematical Model:

Using a linear regression model:

$$ \hat{y} = \beta_0 + \beta_1 x $$

Where β₀ and β₁ are coefficients determined by a training process using available data. Predictions ŷ can then set the future axis range adaptively.

function linearRegression(dataPoints) {
    const xValues = dataPoints.map((d, i) => i);
    const yValues = dataPoints;
    const xMean = xValues.reduce((a, b) => a + b) / xValues.length;
    const yMean = yValues.reduce((a, b) => a + b) / yValues.length;

    let numerator = 0, denominator = 0;
    for (let i = 0; i < xValues.length; i++) {
        numerator += (xValues[i] - xMean) * (yValues[i] - yMean);
        denominator += Math.pow(xValues[i] - xMean, 2);
    }
    const beta1 = numerator / denominator;
    const beta0 = yMean - beta1 * xMean;

    return { beta0, beta1 };
}

// Example usage:
const yValues = [12, 15, 20, 22, 29, 35];
const { beta0, beta1 } = linearRegression(yValues);
const lastIndex = yValues.length - 1;
const predictedextValue = beta0 + beta1 * (lastIndex + 1);    

Machine learning-based range adjustments can be especially powerful for complex data patterns, allowing the graph to preemptively adjust for anticipated fluctuations.


Summary

Each method outlined above provides a unique strategy for adjusting the x and y axes ranges dynamically in a JavaScript graph or simulator:

  1. Automatic Scaling Based on Data Extremes: Adapts ranges to the minimum and maximum data values.
  2. Data-Driven Adaptive Scaling with Statistical Techniques: Utilizes percentiles or other statistical measures to handle outliers.
  3. User-Interactive Zooming and Panning: Allows manual control over graph magnification and viewed data segments.
  4. Dynamic Range Adjustments with Smoothing Intervals: Uses smoothed data values to create stable and visually coherent ranges.
  5. Real-Time Range Adjustments for Streaming Data: Continuously recalculates the range based on the most recent subset of data.
  6. Responsive Range Adjustment Based on Display Dimensions: Ensures graph clarity and proportionality across different screen sizes.
  7. Algorithmic Scaling Techniques for Advanced Control: Employs outlier detection and other algorithms to manage extreme or large datasets.
  8. AI or Machine Learning-Based Range Prediction: Forecasts data trends and adjusts ranges proactively based on predictive models.

Implementing one or combining several of these methodologies will result in adaptive and user-friendly graphing solutions. Each approach offers distinct advantages depending on the context, data nature, and user requirements.


Analysis of Video Stream Extraction


youtube_downloader-1.6.30

The provided JavaScript code represents a Firefox extension named youtube_downloader-1.6.30 designed to facilitate the downloading of YouTube videos by extracting their stream URLs and saving them as local video files. The extension accomplishes this through several key components and functions, each playing a pivotal role in the extraction and downloading process. The following sections delineate the relevant parts of the code responsible for achieving this functionality.

1. Event Handling and Messaging Infrastructure

The extension employs an event-driven architecture to manage communication between different parts of the extension, such as content scripts and background scripts. This is facilitated by the Events module:

var Events = (function () {
    // Initialization of callbacks, listeners, and event handling mechanisms
    chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
        // Dispatching events based on message type
    });

    function _sendMessage(type, data, tab_id) {
        // Sending messages either to specific tabs or the runtime
    }

    function _addListener(type, cb) {
        // Adding event listeners for specific message types
    }

    // Other utility functions for event management

    return {
        sendMessage: _sendMessage,
        addListener: _addListener,
        dispatchEvent: _dispatchEvent,
        addEventListener: _addEventListener
    };
})();

This module is foundational for handling asynchronous operations, such as fetching video information and responding to user interactions within the extension's UI.

2. User Interface Integration

The extension injects a download button into YouTube's watch page, enabling users to initiate the download process. Key functions involved in this process include:

These functions ensure that users have a seamless interface to interact with, allowing them to select and download desired video formats.

3. Fetching and Parsing Video Information

To extract downloadable video streams, the extension retrieves and parses YouTube's video information. This is primarily handled by the g_downloadManager module:

var g_downloadManager = (function () {
    var _jsPlayerCache = {};
    var _cache = {};

    function __loadLinksForVideo(idVideo, callback, errback) {
        // Constructs YouTube info URLs and fetches video information
        function getVideodata(vInfo) {
            // Parses the video information to extract stream URLs and formats
        }
    }

    function _getLinksForVideo(idVideo, tab_id) {
        // Retrieves cached links or initiates fetching if not cached
    }

    function _downloadSignatureDecoderAndDownloadLinks(href, callback, failback) {
        // Downloads and deciphers the signature required to access video streams
        function parsePageContent(html) {
            // Extracts necessary configuration and JavaScript player URL from the page
        }
    }

    function _getParamFromUrl(url, keyName) {
        // Utility function to extract parameters from URLs
    }

    function _getFileExt(mime) {
        // Determines file extension based on MIME type
    }

    function _getFileNameFromUrl(url) {
        // Extracts the video title from the URL for naming the downloaded file
    }

    function _getVideoNameFromUrl(url) {
        // Combines the title and extension to form the complete filename
    }

    var _downloadVideo = (function () {
        var _lastDownloadedUrls = [];

        return function (url) {
            if (_lastDownloadedUrls.indexOf(url) < 0) {
                _lastDownloadedUrls.push(url);

                setTimeout(function () {
                    // prevent second download of the same url
                    var indexEl = _lastDownloadedUrls.indexOf(url);
                    if (indexEl >= 0) {
                        _lastDownloadedUrls.splice(indexEl, 1);
                    }
                }, 5000);

                setTimeout(function () {
                    var params = {
                        "url": url,
                        "saveAs": true,
                        "method": "GET",
                        "conflictAction": "uniquify"
                    };
                    var filename = _getVideoNameFromUrl(url);
                    if (filename) {
                        params["filename"] = filename;
                    }
                    chrome.downloads.download(params, function() {
                        d_log(chrome.runtime.lastError);
                    });
                }, 300);
            }
        };
    })();

    return {
        getLinksForVideo: _getLinksForVideo,
        downloadSignatureDecoderAndDownloadLinks: _downloadSignatureDecoderAndDownloadLinks,
        downloadVideo: _downloadVideo
    }
})();
  1. Fetching Video Information: The function __loadLinksForVideo constructs YouTube's video information URLs and retrieves data necessary for identifying available video streams.
  2. Parsing Stream Maps: Within getVideodata, the extension parses the stream_map parameter to extract individual stream URLs, signatures, and format details.
  3. Signature Deciphering: YouTube often obfuscates video stream URLs using signatures. The extension addresses this by downloading the signature decoder JavaScript from YouTube's player page and executing it to obtain valid signatures. This is handled by functions such as getSignatureDecoder, applyDecoder, and related signature deciphering utilities.

This module ensures that the extension can reliably generate valid URLs for downloading video streams despite YouTube's protective measures.

4. Signature Deciphering Mechanism

YouTube employs signature-based protection for its video streams, necessitating a deciphering mechanism to obtain valid download URLs. The extension includes a comprehensive method to handle this:

var ytHtml5SignatureDecipher = {
    readObfFunc: function(func, data) {
        // Parses and interprets obfuscated signature functions
    },
    getNewChip: function (data) {
        // Extracts transformation actions from YouTube's player code
    },
    getChip: function(data) {
        // Processes the player code to retrieve the necessary transformation steps
    }
};

var getDecodeSignatureFunc = function(data) {
    var actList = ytHtml5SignatureDecipher.getChip(data);
    return function (s) {
        return getSig(actList, s);
    }
}

var getTransformUrlFunc = function(data) {
    var nTransformFunc = getNTransformFunc(data);
    return function (url) {
        var n_param = url.match(/&n=([^&]+)&/);
        if (!n_param) {
            return url;
        }
        n_param = n_param[1];
        return url.replace(n_param, nTransformFunc(n_param));
    }
}

This mechanism ensures that the extension can reliably generate valid URLs for downloading video streams despite YouTube's protective measures.

5. Extracting and Organizing Download Links

Once the video information and signatures are deciphered, the extension organizes the available download links for user selection:

function getLinksFromFormatListAndStreamMap(fmt_list, fmt_stream_map) {
    // Parses format lists and stream maps to extract downloadable URLs
}

function getLinksFromStreamingDataFormats(formats) {
    // Processes streaming data formats to obtain download links
}

function newYouTubeDownloadLink(url, sign, type, resolution, quality, stereo3d, itag) {
    // Constructs a structured object representing a downloadable video link
}

function removeYouTubeDownloadLinksDuplicates(originalLinks) {
    // Filters out duplicate download links to ensure uniqueness
}
  1. Parsing Formats: Functions like getLinksFromFormatListAndStreamMap and getLinksFromStreamingDataFormats parse YouTube's provided format lists to identify available video streams along with their respective qualities and types.
  2. Organizing Links: The newYouTubeDownloadLink function structures each stream's information, including URL, type, resolution, quality, and other relevant metadata.
  3. Eliminating Duplicates: To present a clean list of options to the user, removeYouTubeDownloadLinksDuplicates ensures that each download link is unique, preventing redundant entries.

6. Initiating Downloads

Upon user selection, the extension initiates the download process using Chrome's downloads API:

var _downloadVideo = (function () {
    var _lastDownloadedUrls = [];

    return function (url) {
        if (_lastDownloadedUrls.indexOf(url) < 0) {
            _lastDownloadedUrls.push(url);

            setTimeout(function () {
                // prevent second download of the same url
                var indexEl = _lastDownloadedUrls.indexOf(url);
                if (indexEl >= 0) {
                    _lastDownloadedUrls.splice(indexEl, 1);
                }
            }, 5000);

            setTimeout(function () {
                var params = {
                    "url": url,
                    "saveAs": true,
                    "method": "GET",
                    "conflictAction": "uniquify"
                };
                var filename = _getVideoNameFromUrl(url);
                if (filename) {
                    params["filename"] = filename;
                }
                chrome.downloads.download(params, function() {
                    d_log(chrome.runtime.lastError);
                });
            }, 300);
        }
    };
})();

7. Integration and Initialization

Finally, the extension initializes its components and sets up necessary event listeners to ensure seamless operation:

function main() {
    function openTab(ignore, data) {
        if (typeof data.url !== "undefined") {
            g_downloadManager.downloadVideo(data.url);
        }
    }

    Events.addListener("openBYDTab", openTab);

    Events.addListener("get_links", function (type, idVideo, ignore, tab_id) {
        g_downloadManager.getLinksForVideo(idVideo, tab_id);
    });

    Events.addEventListener("get_sig_decoder", function (event) {
        var data = event.data;
        if (data && data.href) {
            g_downloadManager.downloadSignatureDecoderAndDownloadLinks(data.href, function (data) {
                event.reply({
                    res: true,
                    data: data
                });
            }, function () {
                event.reply({
                    res: false
                });
            });
        }
    });
}

main();

Written on December 3rd, 2024


easy_youtube_video_download-19.1

The provided JavaScript code represents a Firefox extension named easy_youtube_video_download-19.1 designed to facilitate the downloading of YouTube videos by extracting their stream URLs and saving them as local video files. The extension achieves this through several key components and functions, each contributing to the extraction and downloading process. The following sections identify and elaborate on the relevant parts of the code responsible for accomplishing this functionality.

1. Event Handling and Options Management

The extension manages user preferences and handles various installation events through dedicated functions and event listeners. This ensures that user settings are preserved and appropriate actions are taken during installation, updates, or uninstallation.

a. Saving and Restoring Options

The functions save_options and restore_options are responsible for handling user preferences related to autoplay, premium keys, and notification settings. These functions interact with Chrome's storage API to persist and retrieve user settings.

function save_options(e) {
    e.preventDefault();
    var autop = document.getElementById('autoplay').checked;
    var pKey = document.getElementById('prokey').value;
    var noNotify = document.getElementById('notification').checked;
    chrome.storage.sync.set({
        autop: autop,
        pKey: pKey,
        noNotify: noNotify
    }, function () {
        // Update status to let user know options were saved.
        var status = document.getElementById('status');
        status.textContent = 'Options saved.';
        setTimeout(function () {
            status.textContent = '';
        }, 1750);
    });
}

// Restores select box and checkbox state using the preferences stored in chrome.storage.
function restore_options() {
    // Use default value 
    chrome.storage.sync.get({
        autop: false,
        pKey: "",
        noNotify: false,
    }, function (items) {
        document.getElementById('autoplay').checked = items.autop;
        document.getElementById('prokey').value = items.pKey;
        document.getElementById('notification').checked = items.noNotify;
    });

    //do other common check tasks
    bootstart();
}
document.addEventListener('DOMContentLoaded', restore_options);
document.querySelector("form").addEventListener("submit", save_options);

b. Installation and Uninstallation Events

The extension listens for installation, update, and uninstallation events to provide appropriate user feedback and handle cleanup tasks.

//CHECK INSTALL, UPDATE, UNINSTALL
chrome.runtime.onInstalled.addListener(function (details) {
    if (details.reason == "install") {
        chrome.tabs.create({
            url: "https://www.yourvideofile.org/install-success.html",
        });
    }

    if (details.reason == "update") {
        chrome.tabs.create({
            url: "https://www.yourvideofile.org/update-success.html",
        });
    }
});

chrome.runtime.setUninstallURL(
    "https://www.yourvideofile.org/uninstall-success.html"
);

These listeners ensure that users receive confirmations upon installing, updating, or uninstalling the extension, enhancing user experience and providing necessary information.

2. User Interface Integration

The extension integrates seamlessly with YouTube's interface by injecting a download button and corresponding menu into the video watch page. This allows users to initiate the download process directly from the YouTube page.

a. Injecting the Download Button

The parseDetails function is pivotal in injecting the download button into the YouTube page. It determines the appropriate location within the DOM to place the button based on the YouTube UI version.

let dButton = document.createElement("button");
dButton.setAttribute("id", "ytdl_btn");
dButton.setAttribute("class", "ytdl_btn");
dButton.textContent = " " + buttonText + ": ▼" + " ";
dButton.setAttribute("data-tooltip-text", buttonLabel);

// Check if the extension is on the old or new YouTube UI
let isOldUI = true;
if (document.getElementById("comment-teaser")) {
    isOldUI = false;
}

if (parentElement && isOldUI) {
    //OLD UI
    parentElement.childNodes[0].appendChild(dButton);
}

//Are we on a new design, generate and add new style button
if (parentElement && !isOldUI) {
    console.log("Inside new UI");
    parentElement = document.getElementById("owner");
    parentElement.appendChild(dButton);
    buttonEle = document.getElementById("ytdl_btn");
    buttonEle.setAttribute("style", "border-radius: 30px;");
}

if (!parentElement) {
    // Handle cases where the button could not be attached to the standard locations
    let msgCont =
        "

Oops, unable to attach button to the original location on the page - this seems to be some code/layout change by Youtube, for the time-being you can use the button below. Once this design is finalised and pushed to all by Youtube, an updated addon version will place the button to usual location.

You can read more about this here.

"; showPopup("", msgCont, true); parentElement = document.getElementById("notificationPopup"); parentElement.childNodes[2].appendChild(dButton); }

b. Creating the Download Menu

The extension generates a download menu containing various format options based on the extracted video streams. This menu is appended to the download button, allowing users to select their preferred download format.

// create download list
let dList = document.createElement("div");
dList.setAttribute("id", "ytdl_list");
dList.setAttribute("status", "hide");
dList.classList.add("ytdl_list", "ytdl_list_hide");
dButton.appendChild(dList);

for (let i = 0; i < downloadCodeList.length; i++) {
    let getF = downloadCodeList[i].format;
    if (FORMAT_LABEL[getF]) {
        let linkDiv = document.createElement("div");
        linkDiv.setAttribute("class", "eytd_list_item");

        let dLink = document.createElement("a");
        let url = DOMPurify.sanitize(downloadCodeList[i].url);
        dLink.setAttribute("id", "ytdl_link_" + downloadCodeList[i].format);
        dLink.setAttribute("loop", i + "");
        dLink.innerText = downloadCodeList[i].label;

        // Handle direct and external links
        if (downloadCodeList[i].download || downloadCodeList[i].external) {
            dLink.setAttribute("href", url);
            if (downloadCodeList[i].label != "Settings") {
                dLink.setAttribute("download", downloadCodeList[i].download);
            }
            dLink.setAttribute("target", "_blank");
            if (!downloadCodeList[i].external) {
                dLink.addEventListener("click", notifyExtension, false);
            }
        } else {
            live("click", "ytdl_link_" + downloadCodeList[i].format, function () {
                var frm_div = document.getElementById("EXT_DIV");
                if (frm_div) {
                    frm_div.parentElement.removeChild(frm_div);
                }
                var mp3_clean_url =
                    "https://videodroid.org/v3/authenticate.php?vid=" +
                    videoId +
                    "&stoken=" +
                    proKey +
                    "&format=" +
                    FORMAT_LABEL[getF] +
                    "&title=" +
                    videoTitle +
                    "&ver=" +
                    version;
                mp3_clean_url = encodeURI(mp3_clean_url);
                addiframe(mp3_clean_url, "250"); //210
                return false;
            });
        }

        linkDiv.appendChild(dLink);
        dList.appendChild(linkDiv);
    }
}

var downloadBtn = document.getElementById("ytdl_btn");
downloadBtn.addEventListener("click", expandList);

This section ensures that users can interact with the download options, selecting the desired video format for download directly from the YouTube interface.

3. Fetching and Parsing Video Information

Extracting downloadable video streams necessitates retrieving and parsing detailed video information from YouTube. This process involves interacting with YouTube's APIs and handling various data structures to obtain the necessary stream URLs and formats.

a. Fetching Video Data

The functions getRawPageData and getInnerApijson are responsible for fetching raw video data from YouTube. They interact with YouTube's internal APIs to obtain streaming information necessary for constructing download links.

async function getRawPageData() {
    injectScript(
        "var storage=window.localStorage;const videoPage = window?.ytplayer?.config?.args?.raw_player_response;storage.setItem('videoPage',JSON.stringify(videoPage));const $ = (s, x = document) => x.querySelector(s);const basejs =(typeof ytplayer !== 'undefined' && 'config' in ytplayer && ytplayer.config.assets? 'https://' + location.host + ytplayer.config.assets.js: 'web_player_context_config' in ytplayer? 'https://' + location.host + ytplayer.web_player_context_config.jsUrl: null) || $('script[src$=\"base.js\"]').src;storage.setItem('basejs',basejs);"
    );

    let videoPage = window.localStorage.getItem("videoPage");
    console.log("Fetched Raw Page Data:" + videoPage);
    return videoPage;
}

async function getInnerApijson(videoId, clientName, isAgeRestricted) {
    // Define client configurations with apiKey defined separately
    const clients = {
        "IOS_CREATOR": {
            clientDetails: {
                clientName: "IOS_CREATOR",
                clientVersion: "22.33.101",
                deviceModel: "iPhone14,3",
                userAgent: "com.google.ios.ytcreator/22.33.101 (iPhone14,3; U; CPU iOS 15_6 like Mac OS X)",
                hl: "en",
                timeZone: "UTC",
                utcOffsetMinutes: 0
            },
            apiKey: "AIzaSyA8eiZmM1FaDVjRy-dfKTyQ_vz_yYM39w"  // API key for IOS_CREATOR
        },
        "WEB": {
            clientDetails: {
                clientName: "WEB",
                clientVersion: "2.20201021.00.00",
                deviceModel: "",
                userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36",
                hl: "en",
                timeZone: "UTC",
                utcOffsetMinutes: 0
            },
            apiKey: "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8"  // API key for WEB
        },
        "IOS": {
            clientDetails: {
                "clientName": "IOS",
                "clientVersion": "19.09.3",
                "deviceModel": "iPhone14,3",
                "userAgent": "com.google.ios.youtube/19.09.3 (iPhone14,3; U; CPU iOS 15_6 like Mac OS X)",
                "hl": "en",
                "timeZone": "UTC",
                "utcOffsetMinutes": 0
            },
            apiKey: "AIzaSyB-63vPrdThhKuerbB2N_l7Kwwcxj6yUAc"  // API key for IOS
        }
    };

    // Select the appropriate client configuration based on the input
    const { clientDetails, apiKey } = clients[clientName] || clients["IOS_CREATOR"]; // Default to IOS_CREATOR if clientName is not found

    // Customize client information based on age restriction
    const clientInfo = { ...clientDetails };
    if (isAgeRestricted) {
        clientInfo.clientVersion = clientDetails.clientVersion + "_restricted"; // Example suffix for age-restricted content
        clientInfo.clientScreen = "EMBED"; // This detail may be adjusted based on your needs
    }

    // Construct the request body
    const requestBody = {
        context: { client: clientInfo },
        videoId: videoId,
        playbackContext: {
            contentPlaybackContext: {
                html5Preference: "HTML5_PREF_WANTS"
            }
        },
        contentCheckOk: true,
        racyCheckOk: true
    };

    // Define the fetch request details with the updated body structure
    const requestOptions = {
        method: "post",
        headers: {
            Accept: "application/json, text/plain, */*",
            "Content-Type": "application/json",
        },
        body: JSON.stringify(requestBody)
    };

    // Construct the fetch URL using the client's API key
    const url = `https://youtubei.googleapis.com/youtubei/v1/player?key=${apiKey}`;

    try {
        // Execute the fetch request
        const response = await fetch(url, requestOptions);
        if (!response.ok) {
            throw new Error(`HTTP error! Status: ${response.status}`);
        }
        return await response.json();
    } catch (error) {
        console.error("Failed to fetch video page:", error);
        return null; // Optionally return an error object or handle error differently
    }
}

b. Parsing and Deciphering Signatures

YouTube employs signature-based protection to secure its video streams. The extension includes mechanisms to parse and decipher these signatures, enabling the construction of valid download URLs.

const parseDecsig = (data) => {
    try {
        const fnnameresult = /=([a-zA-Z0-9\$]+?)\(decodeURIComponent/.exec(data);
        const fnname = fnnameresult[1];
        const _argnamefnbodyresult = new RegExp(
            escapeRegExp(fnname) + "=function\\((.+?)\\){((.+)=\\2.+?)}"
        ).exec(data);
        const [_, argname, fnbody] = _argnamefnbodyresult;
        const helpernameresult = /;(.+?)\..+?\(/.exec(fnbody);
        const helpername = helpernameresult[1];
        const helperresult = new RegExp(
            "var " + escapeRegExp(helpername) + "={[\\s\\S]+?};"
        ).exec(data);
        const helper = helperresult[0];
        console.log(`parsedecsig result: ${argname}=>{${helper}\n${fnbody}}`);
        return new Function([argname], helper + "\n" + fnbody);
    } catch (e) {
        console.error("parsedecsig error:", e);
        console.info("script content:", data);
        console.info(
            'If you encounter this error, please copy the full "script content" to https://pastebin.com/ for me.'
        );
    }
};

This function analyzes YouTube's obfuscated JavaScript to extract and interpret the signature deciphering logic. By reconstructing the necessary functions, the extension can decode the signatures appended to video stream URLs, ensuring that the download links are valid and accessible.

4. Extracting and Organizing Download Links

After fetching and deciphering the necessary video information and signatures, the extension processes this data to generate a list of downloadable video streams in various formats and qualities.

a. Processing Video Formats

The functions displayFMT and parseDetails are central to organizing the available video formats into user-friendly download options.

async function displayFMT(finalFmt, videoTitle) {
    let jsonData = {};
    let downloadCodeList = [];

    const fmtMap1 = finalFmt.map((format) => {
        jsonData[format.itag] = format._decryptedURL;

        let fmt_url = format._decryptedURL;

        if (fmt_url != undefined && FORMAT_LABEL[format.itag] != undefined) {
            downloadCodeList.push({
                url: DOMPurify.sanitize(fmt_url),
                format: format.itag,
                label: FORMAT_LABEL[format.itag],
                download: videoTitle + "." + FORMAT_TYPE[format.itag],
            });
        }

        //Check 720p Dash availability
        if (format.itag == "22") {
            is720p = true;
        }

        if (format.itag == "136" || format.itag == "247") {
            is720pDash = true;
        }

        //Check 1080p availability
        if (format.itag == "137" || format.itag == "299" || format.itag == "303" || format.itag == "335" || format.itag == "617" || format.itag == "636") {
            is1080p = true;
        }
    });

    //Add additional buttons to the list
    //720p
    if (is720pDash && !is720p) {
        downloadCodeList.push({
            url: DOMPurify.sanitize("https://videodroid.org/"),
            format: "720P",
            label: "MP4 720p (HD)",
        });
    }

    //Full-HD
    if (is1080p) {
        downloadCodeList.push({
            url: DOMPurify.sanitize("https://videodroid.org/"),
            format: "1080p3",
            label: "Full-HD 1080p",
        });
        //reset the value just in case
        is1080p = false;
    }

    downloadCodeList.push({
        url: DOMPurify.sanitize("https://videodroid.org/"),
        format: "mp3256",
        label: "MP3 HQ (256 Kbps)",
    });
    downloadCodeList.push({
        url: DOMPurify.sanitize("https://videodroid.org/"),
        format: "mp3128",
        label: "MP3 HQ (128 Kbps)",
    });

    //Options
    let lnk = browser.runtime.getURL("options/options.html");
    downloadCodeList.push({
        url: lnk,
        format: "Settings",
        label: "Settings",
        external: true,
    });
    //About-Help
    downloadCodeList.push({
        url: DOMPurify.sanitize(
            "https://www.yourvideofile.org/support.html?&ver=" + version
        ),
        format: "About",
        label: "Contact/Bug Report",
        external: true,
    });

    //Donation
    downloadCodeList.push({
        url: DOMPurify.sanitize(
            "https://videodroid.org/pro_upgrade.html?&ver=" + version
        ),
        format: "Donate",
        label: "Donate",
        external: true,
    });

    return downloadCodeList;
}

The displayFMT function processes the final list of video formats, sanitizes the URLs using DOMPurify, and organizes them into a structured list of download options. This list includes various video qualities and formats, as well as additional functionalities such as settings, contact, and donation links.

b. Parsing Video Details

The parseDetails function orchestrates the extraction process by fetching video data, handling potential age restrictions, deciphering signatures, and preparing the download options.

async function parseDetails(url) {
    if (window.location.href.indexOf("shorts/") > -1) {
        let msgCont = "Youtube Shorts video uses a different UI and to download these videos you can use the Download menu provided via the Browser toolbar button.";
        showPopup("", msgCont, true);
        videoId = window.location.href.split("shorts/")[1];
    } else {
        const query = parseQueryString(url.split("?")[1]);
        videoId = query["v"];
    };

    let videoPage = await getInnerApijson(videoId, "IOS_CREATOR", false);

    if (!videoPage.streamingData) {
        //Seems age gated video, refetch data
        videoPage = await getInnerApijson(videoId, "IOS_CREATOR", true);
        console.log("Using Age gated Android");
    }

    //If it still fails, try using page data
    if (!videoPage.streamingData.formats) {
        videoPage = await getRawPageData();
        videoPage = JSON.parse(videoPage);

        //save decsig function for usage later
        var basejs = window.localStorage.getItem("basejs");
        console.log("Scraping page data, and getting base.js : " + basejs)
        var decsig = await fetch(basejs)
            .then((res) => res.text())
            .then((body) => {
                return body;
            });

        var decsig = await parseDecsig(decsig);
        console.log("Youtube Code Changed API Failed");
    }

    //we still have a failure, display error
    if (!videoPage.streamingData.formats) {
        let msgCont = "Error!!";
        //this could be a live video check and inform
        if (
            videoPage.videoDetails.isLive ||
            videoPage.playabilityStatus.reason == "This live event has ended."
        ) {
            msgCont =
                "

This is either an ongoing or recently finished Live stream, it can take upto 12-72 hours to generate download links for such videos, pls. try later after 12-72 hours and the links should be availble by then.

"; showPopup("", msgCont, true); } else { msgCont = "

We were not able to parse the download links from this page, try a page refresh. If this happens with all videos do report this to us."; showPopup("", msgCont, true); } } let videoTitle = document.title .replace(/^\(\d+\)\s*/, "") .replace(/\s*\-\s*YouTube$|'/g, "") .replace(/^\s+|\s+$|\.+$/g, "") .replace(/[\\/:"*?<>|]/g, "") .replace(/[\x00-\x1f\x7f]/g, "") .replace(/[\|\\\/]/g, window.navigator.userAgent.indexOf("Win") >= 0 || window.navigator.userAgent.indexOf("Mac") >= 0 ? "-" : "") .replace(/^(con|prn|aux|nul|com\d|lpt\d)$/i, "") .replace(/#/g, window.navigator.userAgent.indexOf("Win") >= 0 ? "" : "%23") .replace(/&/g, window.navigator.userAgent.indexOf("Win") >= 0 ? "_" : "%26"); const formatURLs = videoPage.streamingData.formats.map((format) => { let url = format.url; const cipher = format.signatureCipher || format.cipher; if (!!cipher) { const components = parseQueryString(cipher); const sig = decsig(components.s); url = components.url + `&${encodeURIComponent(components.sp)}=${encodeURIComponent(sig)}`; } return { itag: format.itag, _decryptedURL: url, }; }); //Populate Adaptive let adaptiveFormats; if (videoPage.streamingData.adaptiveFormats) { adaptiveFormats = videoPage.streamingData.adaptiveFormats; } else { // Fetch using another Client if adaptiveFormats are not available videoPage = await getInnerApijson(videoId, "IOS", false); adaptiveFormats = videoPage.streamingData.adaptiveFormats ? videoPage.streamingData.adaptiveFormats : []; } const adaptiveFormatURLs = videoPage.streamingData.adaptiveFormats.map( (format) => { let url = format.url; const cipher = format.signatureCipher || format.cipher; if (!!cipher) { const components = parseQueryString(cipher); const sig = decsig(components.s); url = components.url + `&${encodeURIComponent(components.sp)}=${encodeURIComponent(sig)}`; } return { itag: format.itag, _decryptedURL: url, }; } ); const finalFmt = [...formatURLs, ...adaptiveFormatURLs]; var downloadCodeList = await displayFMT(finalFmt, videoTitle); let data = { VideoData: downloadCodeList, videoTitle: videoTitle, key: proKey }; sessionStorage.setItem("dList_" + videoId, JSON.stringify(data)); // Prepare button text based on language settings var language = document.documentElement.getAttribute("lang"); language = language.substring(0, 2); var buttonText = BUTTON_TEXT[language] ? BUTTON_TEXT[language] : BUTTON_TEXT["en"]; var buttonLabel = BUTTON_TOOLTIP[language] ? BUTTON_TOOLTIP[language] : BUTTON_TOOLTIP["en"]; // Inject the download button into the YouTube page let dButton = document.createElement("button"); dButton.setAttribute("id", "ytdl_btn"); dButton.setAttribute("class", "ytdl_btn"); dButton.textContent = " " + buttonText + ": ▼" + " "; dButton.setAttribute("data-tooltip-text", buttonLabel); // Additional UI handling omitted for brevity }

The parseDetails function coordinates the overall extraction process by:

4. Signature Deciphering Mechanism

To bypass YouTube's signature-based protection on video streams, the extension includes a signature deciphering mechanism. This involves analyzing YouTube's obfuscated JavaScript to reconstruct the functions necessary for decoding signatures.

a. Deciphering Function Extraction

The parseDecsig function parses the signature deciphering logic from YouTube's JavaScript code. It identifies and reconstructs the necessary functions to decode the signatures appended to video stream URLs.

const parseDecsig = (data) => {
    try {
        const fnnameresult = /=([a-zA-Z0-9\$]+?)\(decodeURIComponent/.exec(data);
        const fnname = fnnameresult[1];
        const _argnamefnbodyresult = new RegExp(
            escapeRegExp(fnname) + "=function\\((.+?)\\){((.+)=\\2.+?)}"
        ).exec(data);
        const [_, argname, fnbody] = _argnamefnbodyresult;
        const helpernameresult = /;(.+?)\..+?\(/.exec(fnbody);
        const helpername = helpernameresult[1];
        const helperresult = new RegExp(
            "var " + escapeRegExp(helpername) + "={[\\s\\S]+?};"
        ).exec(data);
        const helper = helperresult[0];
        console.log(`parsedecsig result: ${argname}=>{${helper}\n${fnbody}}`);
        return new Function([argname], helper + "\n" + fnbody);
    } catch (e) {
        console.error("parsedecsig error:", e);
        console.info("script content:", data);
        console.info(
            'If you encounter this error, please copy the full "script content" to https://pastebin.com/ for me.'
        );
    }
};

By reconstructing the signature deciphering function, the extension ensures that it can generate valid signatures required to access and download video streams.

b. Applying the Deciphered Signature

Once the deciphering function is obtained, it is applied to the encrypted signatures to generate valid URLs for downloading the video streams.

const formatURLs = videoPage.streamingData.formats.map((format) => {
    let url = format.url;
    const cipher = format.signatureCipher || format.cipher;

    if (!!cipher) {
        const components = parseQueryString(cipher);
        const sig = decsig(components.s);
        url =
            components.url +
            `&${encodeURIComponent(components.sp)}=${encodeURIComponent(sig)}`;
    }

    return {
        itag: format.itag,
        _decryptedURL: url,
    };
});

This section ensures that each video stream URL is properly constructed with a valid signature, making the download links accessible and functional.

5. Initiating Downloads

Upon user selection of a desired video format, the extension initiates the download process using Chrome's downloads API. This allows the user to save the video file locally in the chosen format and quality.

a. Handling Download Requests

The extension listens for download requests and processes them accordingly. It sanitizes the filenames to prevent illegal characters and ensures that downloads are not duplicated.

chrome.runtime.onMessage.addListener(function (message) {
    let fname = message.filename
        .trim()
        .replace(/[~!@#$%^&*()_|+\-=?;:'",<>{}[\]\\/]/gi, "-")
        .replace(/[\\/:*?"<>|]/g, "_")
        .substring(0, 240)
        .replace(/\s+/g, " ");

    chrome.downloads.download({
        url: message.url,
        filename: fname,
        conflictAction: "uniquify"
    }, function (downloadId) {
        if (typeof downloadId !== 'undefined') {
            console.log('Download initiated, ID is: ' + downloadId);
        } else {
            console.error('EYTD Download : ' + chrome.runtime.lastError.message);
            alert(chrome.runtime.lastError.message + ", This could be due to illegal characters in video title, try right-click and 'Save Link As' to download.")
        }
    });
});

This listener ensures that download requests are handled efficiently, providing feedback on the download status and alerting the user in case of errors.

b. Download Management

The function _downloadVideo within the g_downloadManager module manages the actual download process. It validates URLs, determines appropriate filenames, and utilizes Chrome's downloads API to initiate the download.

var _downloadVideo = (function () {
    var _lastDownloadedUrls = [];

    return function (url) {
        if (_lastDownloadedUrls.indexOf(url) < 0) {
            _lastDownloadedUrls.push(url);

            setTimeout(function () {
                // prevent second download of the same url
                var indexEl = _lastDownloadedUrls.indexOf(url);
                if (indexEl >= 0) {
                    _lastDownloadedUrls.splice(indexEl, 1);
                }
            }, 5000);

            setTimeout(function () {
                var params = {
                    "url": url,
                    "saveAs": true,
                    "method": "GET",
                    "conflictAction": "uniquify"
                };
                var filename = _getVideoNameFromUrl(url);
                if (filename) {
                    params["filename"] = filename;
                }
                chrome.downloads.download(params, function() {
                    d_log(chrome.runtime.lastError);
                });
            }, 300);
        }
    };
})();

This mechanism ensures that downloads are initiated smoothly while preventing duplicate downloads within a short timeframe, thereby enhancing the extension's reliability and user experience.

6. Fetching and Deciphering Video Streams

The extension employs sophisticated methods to fetch video stream data and decipher the necessary signatures to construct valid download URLs. This involves interacting with YouTube's internal APIs and handling various data formats.

a. Fetching Video Stream Data

The function getInnerApijson interacts with YouTube's internal APIs to retrieve detailed streaming data for a given video ID. It handles different client configurations and accommodates age-restricted content.

async function getInnerApijson(videoId, clientName, isAgeRestricted) {
    // Define client configurations with apiKey defined separately
    const clients = {
        "IOS_CREATOR": {
            clientDetails: {
                clientName: "IOS_CREATOR",
                clientVersion: "22.33.101",
                deviceModel: "iPhone14,3",
                userAgent: "com.google.ios.ytcreator/22.33.101 (iPhone14,3; U; CPU iOS 15_6 like Mac OS X)",
                hl: "en",
                timeZone: "UTC",
                utcOffsetMinutes: 0
            },
            apiKey: "AIzaSyA8eiZmM1FaDVjRy-dfKTyQ_vz_yYM39w"  // API key for IOS_CREATOR
        },
        // Additional client configurations omitted for brevity
    };

    // Select the appropriate client configuration based on the input
    const { clientDetails, apiKey } = clients[clientName] || clients["IOS_CREATOR"]; // Default to IOS_CREATOR if clientName is not found

    // Customize client information based on age restriction
    const clientInfo = { ...clientDetails };
    if (isAgeRestricted) {
        clientInfo.clientVersion = clientDetails.clientVersion + "_restricted"; // Example suffix for age-restricted content
        clientInfo.clientScreen = "EMBED"; // This detail may be adjusted based on your needs
    }

    // Construct the request body
    const requestBody = {
        context: { client: clientInfo },
        videoId: videoId,
        playbackContext: {
            contentPlaybackContext: {
                html5Preference: "HTML5_PREF_WANTS"
            }
        },
        contentCheckOk: true,
        racyCheckOk: true
    };

    // Define the fetch request details with the updated body structure
    const requestOptions = {
        method: "post",
        headers: {
            Accept: "application/json, text/plain, */*",
            "Content-Type": "application/json",
        },
        body: JSON.stringify(requestBody)
    };

    // Construct the fetch URL using the client's API key
    const url = `https://youtubei.googleapis.com/youtubei/v1/player?key=${apiKey}`;

    try {
        // Execute the fetch request
        const response = await fetch(url, requestOptions);
        if (!response.ok) {
            throw new Error(`HTTP error! Status: ${response.status}`);
        }
        return await response.json();
    } catch (error) {
        console.error("Failed to fetch video page:", error);
        return null; // Optionally return an error object or handle error differently
    }
}

This function ensures that the extension can retrieve comprehensive streaming data necessary for constructing download links, even accommodating scenarios involving age restrictions.

b. Deciphering and Constructing Download URLs

After obtaining the streaming data, the extension deciphers any encrypted signatures and constructs valid download URLs for each available video format.

const formatURLs = videoPage.streamingData.formats.map((format) => {
    let url = format.url;
    const cipher = format.signatureCipher || format.cipher;

    if (!!cipher) {
        const components = parseQueryString(cipher);
        const sig = decsig(components.s);
        url =
            components.url +
            `&${encodeURIComponent(components.sp)}=${encodeURIComponent(sig)}`;
    }

    return {
        itag: format.itag,
        _decryptedURL: url,
    };
});

// Similar processing for adaptive formats omitted for brevity

This mapping ensures that each video stream URL is properly decrypted and sanitized, making them ready for user-initiated downloads.

7. Security and Sanitization

To maintain security and prevent potential vulnerabilities, the extension employs sanitization mechanisms to cleanse URLs and user inputs. This is crucial in mitigating risks such as cross-site scripting (XSS).

a. Sanitizing URLs

The extension uses DOMPurify to sanitize URLs before embedding them into the DOM or initiating downloads. This ensures that only safe and valid URLs are processed.

let url = DOMPurify.sanitize(downloadCodeList[i].url);
dLink.setAttribute("href", url);

b. Validating User Inputs

The function isValidEmail validates email inputs to ensure that only correctly formatted emails are accepted, enhancing the integrity of user-provided data.

function isValidEmail(email) {
    const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
    return re.test(String(email).toLowerCase());
}

By validating and sanitizing inputs, the extension upholds high security standards, protecting both the user and the system from malicious exploits.

8. Additional Features and Enhancements

The extension incorporates additional features to enhance user experience and provide extended functionalities beyond basic downloading capabilities.

a. File Size Calculation

The function addFileSize calculates and displays the size of each downloadable file, providing users with information about the download before initiating it.

function addFileSize(url, format) {
    function updateVideoLabel(size, format) {
        var elem = document.getElementById("ytdl_link_" + format);
        if (elem) {
            size = parseInt(size, 10);
            if (size >= 1073741824) {
                size = parseFloat((size / 1073741824).toFixed(1)) + " GB";
            } else if (size >= 1048576) {
                size = parseFloat((size / 1048576).toFixed(1)) + " MB";
            } else {
                size = parseFloat((size / 1024).toFixed(1)) + " KB";
            }
            if (elem.childNodes.length > 1) {
                elem.lastChild.nodeValue = " (" + size + ")";
            } else if (elem.childNodes.length == 1) {
                elem.appendChild(document.createTextNode(" (" + size + ")"));
            }
        }
    }
    let matchSize = findMatch(url, /[&\?]clen=([0-9]+)&/i);
    if (matchSize) {
        updateVideoLabel(matchSize, format);
    } else {
        if (url.indexOf("googlevideo.com") !== -1) {
            fetch(url, {
                method: "HEAD",
            })
                .then((response) => {
                    let size = response.headers.get("content-length");
                    if (size) {
                        updateVideoLabel(size, format);
                    }
                })
                .catch((error) => {
                    console.log("Error Fetch Filesize URL : " + error);
                });
        }
    }
}

b. Dynamic UI Adjustments

The extension dynamically adjusts the user interface based on the YouTube page's structure, ensuring compatibility with different YouTube layouts and versions.

function parseDetails(url) {
    // ... [Code omitted for brevity]

    // Inject the download button into the YouTube page
    let dButton = document.createElement("button");
    dButton.setAttribute("id", "ytdl_btn");
    dButton.setAttribute("class", "ytdl_btn");
    dButton.textContent = " " + buttonText + ": ▼" + " ";
    dButton.setAttribute("data-tooltip-text", buttonLabel);

    // Check if the extension is on the old or new YouTube UI
    let isOldUI = true;
    if (document.getElementById("comment-teaser")) {
        isOldUI = false;
    }

    if (parentElement && isOldUI) {
        //OLD UI
        parentElement.childNodes[0].appendChild(dButton);
    }

    //Are we on a new design, generate and add new style button
    if (parentElement && !isOldUI) {
        console.log("Inside new UI");
        parentElement = document.getElementById("owner");
        parentElement.appendChild(dButton);
        buttonEle = document.getElementById("ytdl_btn");
        buttonEle.setAttribute("style", "border-radius: 30px;");
    }

    // Handle cases where the standard UI elements are not found
    if (!parentElement) {
        let msgCont =
            "

Oops, unable to attach button to the original location on the page - this seems to be some code/layout change by Youtube, for the time-being you can use the button below. Once this design is finalised and pushed to all by Youtube, an updated addon version will place the button to usual location.

You can read more about this here.

"; showPopup("", msgCont, true); parentElement = document.getElementById("notificationPopup"); parentElement.childNodes[2].appendChild(dButton); } // Additional UI handling omitted for brevity }

This flexibility ensures that the extension remains functional even when YouTube updates its interface, providing a consistent user experience.

Written on December 3rd, 2024


Integrated Analysis of Video Stream Extraction and Downloading Mechanisms in Firefox Extensions

The provided JavaScript code pertains to two Firefox extensions, namely youtube_downloader-1.6.30 and easy_youtube_video_download-19.1. Both extensions are designed to facilitate the downloading of YouTube videos by extracting their stream URLs and saving them as local video files. This integrated analysis elucidates the methodologies and components employed by these extensions to accomplish their objectives, drawing insights from both versions of the code.

1. Event Handling and Messaging Infrastructure

Both extensions utilize an event-driven architecture to manage communication between different parts of the extension, such as content scripts and background scripts. This is achieved through dedicated modules and event listeners that handle asynchronous operations, ensuring seamless interaction and responsiveness within the extension's user interface.

a. Event Modules and Listeners

These modules are foundational in handling tasks such as fetching video information, responding to user interactions, and managing the download process.

var Events = (function () {
    chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
        // Dispatching events based on message type
    });

    function _sendMessage(type, data, tab_id) {
        // Sending messages to specific tabs or the runtime
    }

    function _addListener(type, cb) {
        // Adding event listeners for specific message types
    }

    return {
        sendMessage: _sendMessage,
        addListener: _addListener,
        dispatchEvent: _dispatchEvent,
        addEventListener: _addEventListener
    };
})();

b. Initialization of Event Listeners

These listeners ensure that the extension responds appropriately to various events such as opening new tabs, retrieving video links, and handling signature decoders.

function main() {
    function openTab(ignore, data) {
        if (typeof data.url !== "undefined") {
            g_downloadManager.downloadVideo(data.url);
        }
    }

    Events.addListener("openBYDTab", openTab);

    Events.addListener("get_links", function (type, idVideo, ignore, tab_id) {
        g_downloadManager.getLinksForVideo(idVideo, tab_id);
    });

    Events.addEventListener("get_sig_decoder", function (event) {
        var data = event.data;
        if (data && data.href) {
            g_downloadManager.downloadSignatureDecoderAndDownloadLinks(data.href, function (data) {
                event.reply({
                    res: true,
                    data: data
                });
            }, function () {
                event.reply({
                    res: false
                });
            });
        }
    });
}

main();

2. User Interface Integration

The extensions seamlessly integrate with YouTube's interface by injecting download buttons and corresponding menus into the video watch page. This integration allows users to initiate the download process directly from the YouTube page, enhancing user experience and accessibility.

a. Injecting the Download Button

The download button is dynamically created and inserted into the YouTube page's DOM, adapting to different UI layouts to maintain functionality across various YouTube designs.

let dButton = document.createElement("button");
dButton.setAttribute("id", "ytdl_btn");
dButton.setAttribute("class", "ytdl_btn");
dButton.textContent = " Download: ▼ ";
dButton.setAttribute("data-tooltip-text", "Download Video");

// Check if the extension is on the old or new YouTube UI
let isOldUI = true;
if (document.getElementById("comment-teaser")) {
    isOldUI = false;
}

if (parentElement && isOldUI) {
    // OLD UI
    parentElement.childNodes[0].appendChild(dButton);
}

// Are we on a new design, generate and add new style button
if (parentElement && !isOldUI) {
    console.log("Inside new UI");
    parentElement = document.getElementById("owner");
    parentElement.appendChild(dButton);
    buttonEle = document.getElementById("ytdl_btn");
    buttonEle.setAttribute("style", "border-radius: 30px;");
}

if (!parentElement) {
    // Handle cases where the button could not be attached to the standard locations
    let msgCont =
        "

Oops, unable to attach button to the original location on the page - this seems to be some code/layout change by YouTube. For the time being, use the button below. Once the design is finalized and pushed to all by YouTube, an updated addon version will place the button in the usual location.

You can read more about this here.

"; showPopup("", msgCont, true); parentElement = document.getElementById("notificationPopup"); parentElement.childNodes[2].appendChild(dButton); }

b. Creating the Download Menu

The download menu lists various available formats and qualities for the user to select, providing a user-friendly interface for initiating downloads.

// Create download list
let dList = document.createElement("div");
dList.setAttribute("id", "ytdl_list");
dList.setAttribute("status", "hide");
dList.classList.add("ytdl_list", "ytdl_list_hide");
dButton.appendChild(dList);

for (let i = 0; i < downloadCodeList.length; i++) {
    let getF = downloadCodeList[i].format;
    if (FORMAT_LABEL[getF]) {
        let linkDiv = document.createElement("div");
        linkDiv.setAttribute("class", "eytd_list_item");

        let dLink = document.createElement("a");
        let url = DOMPurify.sanitize(downloadCodeList[i].url);
        dLink.setAttribute("id", "ytdl_link_" + downloadCodeList[i].format);
        dLink.innerText = downloadCodeList[i].label;

        if (downloadCodeList[i].download || downloadCodeList[i].external) {
            dLink.setAttribute("href", url);
            if (downloadCodeList[i].label != "Settings") {
                dLink.setAttribute("download", downloadCodeList[i].download);
            }
            dLink.setAttribute("target", "_blank");
            if (!downloadCodeList[i].external) {
                dLink.addEventListener("click", notifyExtension, false);
            }
        } else {
            live("click", "ytdl_link_" + downloadCodeList[i].format, function () {
                var mp3_clean_url =
                    "https://videodroid.org/v3/authenticate.php?vid=" +
                    videoId +
                    "&stoken=" +
                    proKey +
                    "&format=" +
                    FORMAT_LABEL[getF] +
                    "&title=" +
                    videoTitle +
                    "&ver=" +
                    version;
                mp3_clean_url = encodeURI(mp3_clean_url);
                addiframe(mp3_clean_url, "250");
                return false;
            });
        }

        linkDiv.appendChild(dLink);
        dList.appendChild(linkDiv);
    }
}

var downloadBtn = document.getElementById("ytdl_btn");
downloadBtn.addEventListener("click", expandList);

3. Fetching and Parsing Video Information

Extracting downloadable video streams involves retrieving and parsing detailed video information from YouTube. Both extensions interact with YouTube's internal APIs and handle various data structures to obtain the necessary stream URLs and formats.

a. Fetching Video Data

This function constructs and sends a POST request to YouTube's internal API to retrieve video details, accommodating different client configurations and handling age-restricted content.

async function getInnerApijson(videoId, clientName, isAgeRestricted) {
    const clients = {
        "IOS_CREATOR": {
            clientDetails: {
                clientName: "IOS_CREATOR",
                clientVersion: "22.33.101",
                deviceModel: "iPhone14,3",
                userAgent: "com.google.ios.ytcreator/22.33.101 (iPhone14,3; U; CPU iOS 15_6 like Mac OS X)",
                hl: "en",
                timeZone: "UTC",
                utcOffsetMinutes: 0
            },
            apiKey: "AIzaSyA8eiZmM1FaDVjRy-dfKTyQ_vz_yYM39w"  // API key for IOS_CREATOR
        },
        "WEB": {
            clientDetails: {
                clientName: "WEB",
                clientVersion: "2.20201021.00.00",
                deviceModel: "",
                userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36",
                hl: "en",
                timeZone: "UTC",
                utcOffsetMinutes: 0
            },
            apiKey: "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8"  // API key for WEB
        },
        "IOS": {
            clientDetails: {
                "clientName": "IOS",
                "clientVersion": "19.09.3",
                "deviceModel": "iPhone14,3",
                "userAgent": "com.google.ios.youtube/19.09.3 (iPhone14,3; U; CPU iOS 15_6 like Mac OS X)",
                "hl": "en",
                "timeZone": "UTC",
                "utcOffsetMinutes": 0
            },
            apiKey: "AIzaSyB-63vPrdThhKuerbB2N_l7Kwwcxj6yUAc"  // API key for IOS
        }
    };

    // Select the appropriate client configuration based on the input
    const { clientDetails, apiKey } = clients[clientName] || clients["IOS_CREATOR"]; // Default to IOS_CREATOR if clientName is not found

    // Customize client information based on age restriction
    const clientInfo = { ...clientDetails };
    if (isAgeRestricted) {
        clientInfo.clientVersion += "_restricted"; // Example suffix for age-restricted content
        clientInfo.clientScreen = "EMBED"; // This detail may be adjusted based on needs
    }

    // Construct the request body
    const requestBody = {
        context: { client: clientInfo },
        videoId: videoId,
        playbackContext: {
            contentPlaybackContext: {
                html5Preference: "HTML5_PREF_WANTS"
            }
        },
        contentCheckOk: true,
        racyCheckOk: true
    };

    // Define the fetch request details with the updated body structure
    const requestOptions = {
        method: "post",
        headers: {
            Accept: "application/json, text/plain, */*",
            "Content-Type": "application/json",
        },
        body: JSON.stringify(requestBody)
    };

    // Construct the fetch URL using the client's API key
    const url = `https://youtubei.googleapis.com/youtubei/v1/player?key=${apiKey}`;

    try {
        // Execute the fetch request
        const response = await fetch(url, requestOptions);
        if (!response.ok) {
            throw new Error(`HTTP error! Status: ${response.status}`);
        }
        return await response.json();
    } catch (error) {
        console.error("Failed to fetch video page:", error);
        return null; // Optionally return an error object or handle error differently
    }
}

b. Parsing Video Details

This function orchestrates the extraction process by identifying the video ID, fetching video data, handling potential age restrictions, deciphering signatures, and preparing the download options for user interaction.

async function parseDetails(url) {
    if (window.location.href.indexOf("shorts/") > -1) {
        let msgCont = "YouTube Shorts video uses a different UI and to download these videos you can use the Download menu provided via the Browser toolbar button.";
        showPopup("", msgCont, true);
        videoId = window.location.href.split("shorts/")[1];
    } else {
        const query = parseQueryString(url.split("?")[1]);
        videoId = query["v"];
    };

    let videoPage = await getInnerApijson(videoId, "IOS_CREATOR", false);

    if (!videoPage.streamingData) {
        // Seems age-gated video, refetch data
        videoPage = await getInnerApijson(videoId, "IOS_CREATOR", true);
        console.log("Using Age-gated Android");
    }

    // If it still fails, try using page data
    if (!videoPage.streamingData.formats) {
        videoPage = await getRawPageData();
        videoPage = JSON.parse(videoPage);

        // Save decsig function for usage later
        var basejs = window.localStorage.getItem("basejs");
        console.log("Scraping page data, and getting base.js : " + basejs)
        var decsig = await fetch(basejs)
            .then((res) => res.text())
            .then((body) => parseDecsig(body));
        console.log("YouTube Code Changed API Failed");
    }

    // We still have a failure, display error
    if (!videoPage.streamingData.formats) {
        let msgCont = "Error!!";
        // This could be a live video check and inform
        if (
            videoPage.videoDetails.isLive ||
            videoPage.playabilityStatus.reason == "This live event has ended."
        ) {
            msgCont =
                "

This is either an ongoing or recently finished Live stream. It can take up to 12-72 hours to generate download links for such videos. Please try again later.

"; showPopup("", msgCont, true); } else { msgCont = "

Unable to parse the download links from this page. Please try refreshing the page. If this issue persists with all videos, please report it.

"; showPopup("", msgCont, true); } } let videoTitle = document.title .replace(/^\(\d+\)\s*/, "") .replace(/\s*\-\s*YouTube$|'/g, "") .replace(/[\\/:"*?<>|#&]/g, "") .trim(); const formatURLs = videoPage.streamingData.formats.map((format) => { let url = format.url; const cipher = format.signatureCipher || format.cipher; if (cipher) { const components = parseQueryString(cipher); const sig = decsig(components.s); url = `${components.url}&${encodeURIComponent(components.sp)}=${encodeURIComponent(sig)}`; } return { itag: format.itag, _decryptedURL: url, }; }); // Populate Adaptive Formats let adaptiveFormats; if (videoPage.streamingData.adaptiveFormats) { adaptiveFormats = videoPage.streamingData.adaptiveFormats; } else { // Fetch using another Client if adaptiveFormats are not available videoPage = await getInnerApijson(videoId, "IOS", false); adaptiveFormats = videoPage.streamingData.adaptiveFormats ? videoPage.streamingData.adaptiveFormats : []; } const adaptiveFormatURLs = videoPage.streamingData.adaptiveFormats.map( (format) => { let url = format.url; const cipher = format.signatureCipher || format.cipher; if (cipher) { const components = parseQueryString(cipher); const sig = decsig(components.s); url = `${components.url}&${encodeURIComponent(components.sp)}=${encodeURIComponent(sig)}`; } return { itag: format.itag, _decryptedURL: url, }; } ); const finalFmt = [...formatURLs, ...adaptiveFormatURLs]; var downloadCodeList = await displayFMT(finalFmt, videoTitle); // Store download list and prepare UI elements sessionStorage.setItem("dList_" + videoId, JSON.stringify({ VideoData: downloadCodeList, videoTitle: videoTitle, key: proKey })); // Prepare button text based on language settings var language = document.documentElement.getAttribute("lang"); language = language.substring(0, 2); var buttonText = BUTTON_TEXT[language] ? BUTTON_TEXT[language] : BUTTON_TEXT["en"]; var buttonLabel = BUTTON_TOOLTIP[language] ? BUTTON_TOOLTIP[language] : BUTTON_TOOLTIP["en"]; // Inject the download button into the YouTube page let dButton = document.createElement("button"); dButton.setAttribute("id", "ytdl_btn"); dButton.setAttribute("class", "ytdl_btn"); dButton.textContent = " " + buttonText + ": ▼ " + " "; dButton.setAttribute("data-tooltip-text", buttonLabel); // Additional UI handling omitted for brevity }

4. Signature Deciphering Mechanism

YouTube employs signature-based protection to secure its video streams. Both extensions incorporate mechanisms to parse and decipher these signatures, enabling the construction of valid download URLs.

a. Deciphering Function Extraction

This function analyzes YouTube's obfuscated JavaScript to extract and reconstruct the signature deciphering logic. By identifying and rebuilding the necessary functions, the extension can decode the signatures appended to video stream URLs, ensuring the validity and accessibility of the download links.

const parseDecsig = (data) => {
    try {
        const fnnameresult = /=([a-zA-Z0-9\$]+?)\(decodeURIComponent/.exec(data);
        const fnname = fnnameresult[1];
        const _argnamefnbodyresult = new RegExp(
            escapeRegExp(fnname) + "=function\\((.+?)\\){((.+)=\\2.+?)}"
        ).exec(data);
        const [_, argname, fnbody] = _argnamefnbodyresult;
        const helpernameresult = /;(.+?)\..+?\(/.exec(fnbody);
        const helpername = helpernameresult[1];
        const helperresult = new RegExp(
            "var " + escapeRegExp(helpername) + "={[\\s\\S]+?};"
        ).exec(data);
        const helper = helperresult[0];
        console.log(`parsedecsig result: ${argname}=>{${helper}\n${fnbody}}`);
        return new Function([argname], helper + "\n" + fnbody);
    } catch (e) {
        console.error("parsedecsig error:", e);
        console.info("script content:", data);
        console.info(
            'If you encounter this error, please copy the full "script content" to https://pastebin.com/ for me.'
        );
    }
};

b. Applying the Deciphered Signature

This mapping ensures that each video stream URL is properly decrypted and sanitized, making them ready for user-initiated downloads.

const formatURLs = videoPage.streamingData.formats.map((format) => {
    let url = format.url;
    const cipher = format.signatureCipher || format.cipher;

    if (cipher) {
        const components = parseQueryString(cipher);
        const sig = decsig(components.s);
        url = `${components.url}&${encodeURIComponent(components.sp)}=${encodeURIComponent(sig)}`;
    }

    return {
        itag: format.itag,
        _decryptedURL: url,
    };
});

5. Extracting and Organizing Download Links

After fetching and deciphering the necessary video information and signatures, the extensions process this data to generate a list of downloadable video streams in various formats and qualities.

a. Processing Video Formats

The displayFMT function processes the final list of video formats, sanitizes the URLs using DOMPurify, and organizes them into a structured list of download options. This list includes various video qualities and formats, as well as additional functionalities such as settings, contact, and donation links.

async function displayFMT(finalFmt, videoTitle) {
    let jsonData = {};
    let downloadCodeList = [];

    finalFmt.forEach((format) => {
        jsonData[format.itag] = format._decryptedURL;

        let fmt_url = format._decryptedURL;

        if (fmt_url && FORMAT_LABEL[format.itag]) {
            downloadCodeList.push({
                url: DOMPurify.sanitize(fmt_url),
                format: format.itag,
                label: FORMAT_LABEL[format.itag],
                download: `${videoTitle}.${FORMAT_TYPE[format.itag]}`,
            });
        }

        // Check availability of higher resolutions
        if (format.itag == "22") is720p = true;
        if (["136", "247"].includes(format.itag)) is720pDash = true;
        if (["137", "299", "303", "335", "617", "636"].includes(format.itag)) is1080p = true;
    });

    // Add additional high-quality download options
    if (is720pDash && !is720p) {
        downloadCodeList.push({
            url: DOMPurify.sanitize("https://videodroid.org/"),
            format: "720P",
            label: "MP4 720p (HD)",
        });
    }

    if (is1080p) {
        downloadCodeList.push({
            url: DOMPurify.sanitize("https://videodroid.org/"),
            format: "1080p3",
            label: "Full-HD 1080p",
        });
        is1080p = false;
    }

    // Add audio formats and additional options
    downloadCodeList.push(
        { url: DOMPurify.sanitize("https://videodroid.org/"), format: "mp3256", label: "MP3 HQ (256 Kbps)" },
        { url: DOMPurify.sanitize("https://videodroid.org/"), format: "mp3128", label: "MP3 HQ (128 Kbps)" },
        { url: browser.runtime.getURL("options/options.html"), format: "Settings", label: "Settings", external: true },
        { url: DOMPurify.sanitize("https://www.yourvideofile.org/support.html?&ver=" + version), format: "About", label: "Contact/Bug Report", external: true },
        { url: DOMPurify.sanitize("https://videodroid.org/pro_upgrade.html?&ver=" + version), format: "Donate", label: "Donate", external: true }
    );

    return downloadCodeList;
}

b. Generating Download Links

This loop iterates through the list of available formats, creating and appending download links to the download menu. It distinguishes between direct download links and external functionalities, ensuring that each option behaves as intended.

for (let i = 0; i < downloadCodeList.length; i++) {
    let getF = downloadCodeList[i].format;
    if (FORMAT_LABEL[getF]) {
        let linkDiv = document.createElement("div");
        linkDiv.setAttribute("class", "eytd_list_item");

        let dLink = document.createElement("a");
        let url = DOMPurify.sanitize(downloadCodeList[i].url);
        dLink.setAttribute("id", "ytdl_link_" + downloadCodeList[i].format);
        dLink.innerText = downloadCodeList[i].label;

        if (downloadCodeList[i].download || downloadCodeList[i].external) {
            dLink.setAttribute("href", url);
            if (downloadCodeList[i].label != "Settings") {
                dLink.setAttribute("download", downloadCodeList[i].download);
            }
            dLink.setAttribute("target", "_blank");
            if (!downloadCodeList[i].external) {
                dLink.addEventListener("click", notifyExtension, false);
            }
        } else {
            live("click", "ytdl_link_" + downloadCodeList[i].format, function () {
                var mp3_clean_url =
                    "https://videodroid.org/v3/authenticate.php?vid=" +
                    videoId +
                    "&stoken=" +
                    proKey +
                    "&format=" +
                    FORMAT_LABEL[getF] +
                    "&title=" +
                    videoTitle +
                    "&ver=" +
                    version;
                mp3_clean_url = encodeURI(mp3_clean_url);
                addiframe(mp3_clean_url, "250");
                return false;
            });
        }

        linkDiv.appendChild(dLink);
        dList.appendChild(linkDiv);
    }
}

5. Initiating Downloads

Upon user selection of a desired video format, the extensions initiate the download process using Chrome's downloads API. This allows users to save the video file locally in their chosen format and quality.

a. Handling Download Requests

This listener ensures that download requests are handled efficiently, providing feedback on the download status and alerting the user in case of errors such as illegal characters in filenames.

chrome.runtime.onMessage.addListener(function (message) {
    let fname = message.filename
        .trim()
        .replace(/[~!@#$%^&*()_|+\-=?;:'",<>{}[\]\\/]/gi, "-")
        .replace(/[\\/:*?"<>|]/g, "_")
        .substring(0, 240)
        .replace(/\s+/g, " ");

    chrome.downloads.download({
        url: message.url,
        filename: fname,
        conflictAction: "uniquify"
    }, function (downloadId) {
        if (typeof downloadId !== 'undefined') {
            console.log('Download initiated, ID is: ' + downloadId);
        } else {
            console.error('Download Error: ' + chrome.runtime.lastError.message);
            alert(chrome.runtime.lastError.message + ", This could be due to illegal characters in video title, try right-click and 'Save Link As' to download.");
        }
    });
});

b. Download Management Function

This function manages the download process by validating URLs, determining appropriate filenames, and utilizing Chrome's downloads API to initiate downloads while preventing duplicate requests within a short timeframe.

var _downloadVideo = (function () {
    var _lastDownloadedUrls = [];

    return function (url) {
        if (_lastDownloadedUrls.indexOf(url) < 0) {
            _lastDownloadedUrls.push(url);

            setTimeout(function () {
                // Prevent duplicate downloads
                var indexEl = _lastDownloadedUrls.indexOf(url);
                if (indexEl >= 0) {
                    _lastDownloadedUrls.splice(indexEl, 1);
                }
            }, 5000);

            setTimeout(function () {
                var params = {
                    "url": url,
                    "saveAs": true,
                    "method": "GET",
                    "conflictAction": "uniquify"
                };
                var filename = _getVideoNameFromUrl(url);
                if (filename) {
                    params["filename"] = filename;
                }
                chrome.downloads.download(params, function() {
                    console.error(chrome.runtime.lastError);
                });
            }, 300);
        }
    };
})();

6. Security and Sanitization

To maintain security and prevent potential vulnerabilities, the extensions employ sanitization mechanisms to cleanse URLs and user inputs. This is crucial in mitigating risks such as cross-site scripting (XSS).

a. Sanitizing URLs

The use of DOMPurify ensures that all URLs embedded into the DOM are sanitized, preventing the injection of malicious scripts or unwanted content.

let url = DOMPurify.sanitize(downloadCodeList[i].url);
dLink.setAttribute("href", url);

b. Validating User Inputs

These validations ensure that user-provided data, such as email addresses, adhere to expected formats, thereby enhancing the integrity and security of the extension.

function isValidEmail(email) {
    const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
    return re.test(String(email).toLowerCase());
}

emailInput.addEventListener("blur", (e) => {
    if (!isValidEmail(e.target.value) || e.target.value.trim() === "") {
        e.target.classList.add("is-invalid");
        errorMessage.innerText = "Invalid email address";
    } else {
        e.target.classList.remove("is-invalid");
        errorMessage.innerText = "";
    }
});

7. Additional Features and Enhancements

Beyond the core functionalities of extracting and downloading video streams, both extensions incorporate additional features to enhance user experience and provide extended functionalities.

a. File Size Calculation

This function calculates and displays the size of each downloadable file, providing users with information about the download before initiating it.

function addFileSize(url, format) {
    function updateVideoLabel(size, format) {
        var elem = document.getElementById("ytdl_link_" + format);
        if (elem) {
            size = parseInt(size, 10);
            if (size >= 1073741824) {
                size = parseFloat((size / 1073741824).toFixed(1)) + " GB";
            } else if (size >= 1048576) {
                size = parseFloat((size / 1048576).toFixed(1)) + " MB";
            } else {
                size = parseFloat((size / 1024).toFixed(1)) + " KB";
            }
            if (elem.childNodes.length > 1) {
                elem.lastChild.nodeValue = " (" + size + ")";
            } else if (elem.childNodes.length == 1) {
                elem.appendChild(document.createTextNode(" (" + size + ")"));
            }
        }
    }
    let matchSize = findMatch(url, /[&\?]clen=([0-9]+)&/i);
    if (matchSize) {
        updateVideoLabel(matchSize, format);
    } else {
        if (url.indexOf("googlevideo.com") !== -1) {
            fetch(url, {
                method: "HEAD",
            })
                .then((response) => {
                    let size = response.headers.get("content-length");
                    if (size) {
                        updateVideoLabel(size, format);
                    }
                })
                .catch((error) => {
                    console.log("Error Fetch Filesize URL : " + error);
                });
        }
    }
}

b. Dynamic UI Adjustments

These snippets ensure that the extension adapts to changes in YouTube's page structure, maintaining functionality even when YouTube updates its interface.

function insertAfter(el, referenceNode) {
    referenceNode.parentNode.insertBefore(el, referenceNode.nextSibling);
}

addEventListener("yt-page-data-updated", (event) => {
    restoreOptions();
    parseDetails(window.location.href);
    removeOldElements();
    let frm_div = document.getElementById("EXT_DIV");
    if (frm_div) {
        frm_div.remove();
    }
});

8. Integration and Initialization

Both extensions initialize their components and set up necessary event listeners to ensure seamless operation from the moment they are installed or activated.

a. Initialization of Options and Event Listeners

function restoreOptions() {
    chrome.storage.sync.get({
        autop: false,
        pKey: "",
        noNotify: false,
    }, function (items) {
        document.getElementById('autoplay').checked = items.autop;
        document.getElementById('prokey').value = items.pKey;
        document.getElementById('notification').checked = items.noNotify;
    });

    bootstart();
}

document.addEventListener('DOMContentLoaded', restoreOptions);
document.querySelector("form").addEventListener("submit", save_options);

These functions ensure that user preferences are loaded and applied upon the extension's activation, providing a personalized and consistent user experience.

9. Security and Compliance

Both extensions adhere to security best practices to protect user data and maintain compliance with browser policies.

a. Trusted Types and DOMPurify Integration

The integration of DOMPurify ensures that all user inputs and dynamically generated content are sanitized, preventing the injection of malicious scripts and safeguarding against XSS attacks.

/*! @license DOMPurify 2.3.1 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/2.3.1/LICENSE */
// DOMPurify library code included for sanitizing HTML and URLs

b. Validating and Sanitizing Inputs

function isValidEmail(email) {
    const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
    return re.test(String(email).toLowerCase());
}

emailInput.addEventListener("blur", (e) => {
    if (!isValidEmail(e.target.value) || e.target.value.trim() === "") {
        e.target.classList.add("is-invalid");
        errorMessage.innerText = "Invalid email address";
    } else {
        e.target.classList.remove("is-invalid");
        errorMessage.innerText = "";
    }
});

By validating and sanitizing inputs, the extensions uphold high security standards, protecting both the user and the system from malicious exploits.

Written on December 3rd, 2024


nGeneFirefoxDownloader Extension Documentation v1.0 (Written December 3rd, 2024)

This document provides a comprehensive overview of the nGeneFirefoxDownloader Firefox extension, designed to facilitate the downloading of YouTube videos. Each component of the extension is presented with its corresponding source code followed by a detailed explanation to aid in understanding and further development.


1. manifest.json

{
  "name": "nGeneFirefoxDownloader",
  "description": "Simple YouTube video downloader for Firefox.",
  "manifest_version": 2,
  "version": "1.0",
  "icons": {
    "48": "icons/icon.png"
  },
  "background": {
    "scripts": ["background.js"]
  },
  "content_scripts": [
    {
      "js": ["content_script.js"],
      "matches": [
        "http://www.youtube.com/*",
        "https://www.youtube.com/*",
        "http://m.youtube.com/*"
      ],
      "exclude_matches": [
        "http://www.youtube.com/embed/*",
        "https://www.youtube.com/embed/*"
      ],
      "run_at": "document_end"
    }
  ],
  "permissions": ["downloads", "activeTab"],
  "browser_action": {
    "default_title": "nGeneFirefoxDownloader",
    "default_icon": {
      "16": "icons/icon.png",
      "32": "icons/icon32.png"
    }
  }
}

The manifest.json file serves as the blueprint for the Firefox extension, outlining its fundamental properties and configurations. Below is a breakdown of its key components:

This manifest ensures that the extension is appropriately registered with Firefox, defines its operational scope, and requests the necessary permissions to perform video downloads from YouTube.


2. content_script.js

(function () {
  function getStreamingData() {
    // Try to get ytInitialPlayerResponse from the page
    let playerResponse = null;

    if (window.ytInitialPlayerResponse) {
      playerResponse = window.ytInitialPlayerResponse;
    } else {
      // Try to find it in the scripts
      let scripts = document.getElementsByTagName('script');
      for (let i = 0; i < scripts.length; i++) {
        let scriptContent = scripts[i].textContent;
        if (scriptContent.includes('ytInitialPlayerResponse')) {
          let jsonStr = scriptContent.match(
            /ytInitialPlayerResponse\s*=\s*(\{.*?\});/s
          );
          if (jsonStr && jsonStr[1]) {
            playerResponse = JSON.parse(jsonStr[1]);
            break;
          }
        }
      }
    }

    return playerResponse;
  }

  function getVideoTitle() {
    let titleElement = document.querySelector('h1.title');
    let title = titleElement ? titleElement.textContent.trim() : document.title.replace(' - YouTube', '').trim();
    return title;
  }

  function sanitizeFilename(name) {
    // Remove invalid characters
    name = name.replace(/[<>:"\/\\|?*\x00-\x1F]/g, '').trim();
    // Replace spaces with underscores
    name = name.replace(/\s+/g, '_');
    // Limit the length of the filename
    let maxFilenameLength = 100; // Adjust as needed
    if (name.length > maxFilenameLength) {
      name = name.substring(0, maxFilenameLength);
    }
    return name;
  }

  function addButton() {
    // Create the download button
    let downloadBtn = document.createElement('button');
    downloadBtn.textContent = 'Download Video';
    downloadBtn.style.position = 'fixed';
    downloadBtn.style.top = '10px';
    downloadBtn.style.right = '10px';
    downloadBtn.style.zIndex = 9999;
    downloadBtn.style.padding = '10px';
    downloadBtn.style.backgroundColor = '#ff0000';
    downloadBtn.style.color = '#ffffff';
    downloadBtn.style.border = 'none';
    downloadBtn.style.borderRadius = '5px';
    downloadBtn.style.cursor = 'pointer';
    document.body.appendChild(downloadBtn);

    downloadBtn.addEventListener('click', function () {
      let playerResponse = getStreamingData();
      if (!playerResponse) {
        alert('Failed to retrieve video data.');
        return;
      }

      let formats = playerResponse.streamingData.formats || [];
      let adaptiveFormats = playerResponse.streamingData.adaptiveFormats || [];

      // Merge formats
      let allFormats = formats.concat(adaptiveFormats);

      // Find 480p (itag 135) or 360p (itag 18 or 134)
      let targetFormat = allFormats.find(
        (f) => f.itag == 135 || f.itag == 18 || f.itag == 134
      );

      if (!targetFormat) {
        alert('480p or 360p format not available.');
        return;
      }

      let videoUrl = targetFormat.url;
      if (!videoUrl) {
        // Need to decipher signature
        let cipher = targetFormat.signatureCipher || targetFormat.cipher;
        if (cipher) {
          let urlParams = new URLSearchParams(cipher);
          let url = urlParams.get('url');
          let s = urlParams.get('s');
          let sp = urlParams.get('sp') || 'signature';
          if (s && url) {
            // Cannot decipher signature, so cannot download
            alert(
              'Cannot download this video due to signature encryption.'
            );
            return;
          } else {
            videoUrl = url;
          }
        } else {
          alert('Video URL not found.');
          return;
        }
      }

      let title = getVideoTitle();
      let filename = sanitizeFilename(title) + '.mp4';


      // Send message to background script to download the video
      chrome.runtime.sendMessage({
        action: 'downloadVideo',
        url: videoUrl,
        filename: filename
      });
    });
  }

  function init() {
    // Wait for the page to load
    if (document.readyState !== 'complete') {
      window.addEventListener('load', addButton);
    } else {
      addButton();
    }
  }

  init();
})();

The content_script.js file is responsible for interacting directly with YouTube web pages to facilitate the video downloading process. The script performs several key functions as outlined below:

  1. Encapsulation:
    • The entire script is encapsulated within an Immediately Invoked Function Expression (IIFE) to avoid polluting the global namespace and ensure that variables and functions do not interfere with other scripts on the page.
  2. Retrieving Streaming Data (getStreamingData):
    • Purpose: Extracts the streaming data necessary to identify available video formats and their respective URLs.
    • Methodology:
      1. Attempts to access ytInitialPlayerResponse directly from the window object.
      2. If unavailable, iterates through all <script> tags on the page to locate and parse the JSON object containing ytInitialPlayerResponse.
      3. Returns the parsed playerResponse object containing streaming data or null if unsuccessful.
  3. Fetching Video Title (getVideoTitle):
    • Purpose: Obtains the title of the currently viewed YouTube video to use as the filename for the downloaded video.
    • Methodology:
      1. Attempts to select the <h1> element with the class title.
      2. If the element is found, extracts and trims its text content.
      3. If not, defaults to using the document's title, removing the " - YouTube" suffix.
  4. Sanitizing Filename (sanitizeFilename):
    • Purpose: Cleans the video title to create a valid filename by removing prohibited characters and formatting the string.
    • Methodology:
      1. Removes characters that are invalid in filenames (<>:"/\|?* and control characters).
      2. Replaces spaces with underscores to enhance readability and compatibility.
      3. Truncates the filename to a maximum length (e.g., 100 characters) to prevent filesystem errors.
  5. Adding the Download Button (addButton):
    • Purpose: Injects a "Download Video" button into the YouTube page interface, enabling users to initiate the download process.
    • Methodology:
      1. Creates a <button> element with the text "Download Video".
      2. Styles the button with fixed positioning, prominent colors, padding, and other CSS properties to ensure visibility and usability.
      3. Appends the button to the document's body.
      4. Attaches a click event listener to handle the download process when the button is pressed.
  6. Download Process on Button Click:
    • Steps:
      1. Retrieve Streaming Data: Calls getStreamingData() to obtain the necessary video data.
      2. Error Handling: Alerts the user if streaming data cannot be retrieved.
      3. Format Selection: Merges available video formats and selects the desired quality (e.g., 480p or 360p) based on specific itag values.
      4. URL Extraction:
        • Attempts to extract the direct video URL from the selected format.
        • If the URL is not directly available, handles signature encryption scenarios by parsing the signatureCipher or cipher fields.
        • Alerts the user if the video URL cannot be determined due to encryption.
      5. Filename Preparation: Retrieves and sanitizes the video title to create a suitable filename with an .mp4 extension.
      6. Message Dispatch: Sends a message to the background script with the action downloadVideo, including the video URL and the sanitized filename.
  7. Initialization (init):
    • Purpose: Ensures that the download button is added only after the page has fully loaded.
    • Methodology:
      1. Checks the document's readiness state.
      2. If the document is not yet complete, adds an event listener for the load event to call addButton().
      3. If the document is already loaded, calls addButton() immediately.

This content script effectively integrates a user interface element into YouTube pages, enabling users to download videos by interacting with the injected button. It handles the extraction and sanitization of necessary data while ensuring compatibility and error handling for various scenarios.


3. background.js

chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
  if (request.action === 'downloadVideo') {
    chrome.downloads.download(
      {
        url: request.url,
        filename: 'Videos/' + request.filename, // This will save to a 'Videos' folder within the default download directory
        saveAs: false
      },
      function (downloadId) {
        if (downloadId) {
          console.log('Download initiated with ID:', downloadId);
        } else {
          console.error('Download failed:', chrome.runtime.lastError);
        }
      }
    );
  }
});

The background.js file operates within the extension's background context, handling tasks that require persistent operation, such as initiating downloads. Below is an elucidation of its functionality:

  1. Message Listener (chrome.runtime.onMessage.addListener):
    • Purpose: Listens for messages dispatched from other parts of the extension, specifically from the content script.
    • Functionality:
      • Receives messages containing various actions and associated data.
      • Checks if the received message's action property matches 'downloadVideo', indicating a request to download a video.
  2. Handling downloadVideo Action:
    • Parameters:
      • url: The direct URL to the video file to be downloaded.
      • filename: The sanitized name for the downloaded video file.
    • Operation:
      • Utilizes the chrome.downloads.download API to initiate the download process.
      • Download Options:
        • url: Specifies the direct link to the video to be downloaded.
        • filename: Determines the path and name of the downloaded file. In this case, the video is saved within a "Videos" folder in the user's default download directory, with the filename provided by the content script.
        • saveAs: Set to false to bypass the "Save As" dialog, allowing the download to proceed automatically.
    • Callback Function:
      • Success: If the download is successfully initiated, logs the download ID to the console for reference.
      • Failure: If the download fails to start, logs an error message detailing the reason, utilizing chrome.runtime.lastError for diagnostic purposes.

This background script plays a crucial role in managing the download operations triggered by the user through the content script. By handling messages and interfacing with the Chrome Downloads API, it ensures that video files are downloaded efficiently and appropriately, adhering to the specified parameters.

Written on December 3rd, 2024


Back to Top