Calculator Tools Calculator Tools
Create

Image to IPTV Converter & M3U Playlist Generator

Aug 20, 2026

About this app

Convert images, screenshots, IPTV flyers, logos, and Xtream details into playable M3U IPTV playlists with built-in player and QR exporter.

Image to IPTV Converter & M3U Playlist Generator Image to IPTV Converter Extract M3U URLs & Xtream details from screenshots/flyers, convert logos into play lists, & stream live. Total Channels 0 Groups 0 Image Scanner Playlist & Builder IPTV Player Xtream Converter Export & Share Upload IPTV Screenshot / Image Drag & Drop IPTV Image or Click to Browse Supports TV screenshots, IPTV subscription flyers, logo URLs & QR codes Select Image File OCR Pattern Analysis Scanning pixels for URLs, .m3u8, and Xtream logins... Upload an IPTV image flyer or channel screenshot to auto-extract stream URLs. Extracted Text Output Add Extracted Stream to Playlist Save to Active M3U Playlist Add Custom Channel Clear All Select a channel to start playback HLS / HTML5 Native Your browser does not support the video tag. Playback Tip: Direct HLS (.m3u8) streams play natively on Safari and iOS. For Android/Chrome browsers requiring CORS or HLS JS, export your M3U playlist to external IPTV apps like GSE Smart IPTV, TiviMate, or VLC. Xtream Codes API to M3U Converter Server URL / Host Username Password Output Format M3U Plus (With Logos & Categories) Standard M3U Generate M3U URL & Import Channels Generate...

Related apps

Put this on your site

Source

                    <html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">

<title>Image to IPTV Converter & M3U Playlist Generator</title>
<meta name="description" content="Convert images, screenshots, IPTV flyers, logos, and Xtream details into playable M3U IPTV playlists with built-in player and QR exporter.">
<meta name="keywords" content="iptv, m3u converter, image to m3u, iptv playlist generator, xtream codes, hls player, m3u8 player, iptv scanner">

<!-- Libraries -->
<!-- jQuery (3.6.0) -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js" crossorigin="anonymous"></script>
<!-- Bootstrap CSS (5.3.3) -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" crossorigin="anonymous">
<!-- Bootstrap JS (5.3.3) -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.min.js" crossorigin="anonymous"></script>
<!-- Font Awesome (6.6.0) -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css" crossorigin="anonymous">

<script type="text/javascript">
try {
// Main Application Logic
let iptvChannels = [
{ id: 1, name: "NASA TV HD", group: "Science & Space", logo: "https://images.unsplash.com/photo-1614728894747-a83421e2b9c9?w=150&auto=format&fit=crop", url: "https://ntv1.akamaized.net/hls/live/2014075/NASA-TV-Public/master.m3u8", status: "Active" },
{ id: 2, name: "DW News Live", group: "News", logo: "https://images.unsplash.com/photo-1585829365295-ab7cd400c167?w=150&auto=format&fit=crop", url: "https://dwamdstream102.akamaized.net/hls/live/2015525/dwstream102/index.m3u8", status: "Active" },
{ id: 3, name: "France 24 English", group: "International", logo: "https://images.unsplash.com/photo-1504711434969-e33886168f5c?w=150&auto=format&fit=crop", url: "https://static.france24.com/live/F24_EN_LO_HLS/live_tv.m3u8", status: "Active" },
{ id: 4, name: "Red Bull TV", group: "Sports & Gaming", logo: "https://images.unsplash.com/photo-1517649763962-0c623266010b?w=150&auto=format&fit=crop", url: "https://rbmn-live.akamaized.net/hls/live/590920/BoB-rbmn-live/master.m3u8", status: "Active" }
];

let extractedTextResult = "";
let activeVideoUrl = "";

document.addEventListener("DOMContentLoaded", function() {
renderChannelList();
updateStats();

// Tab Switching
$('.nav-link-custom').on('click', function(e) {
e.preventDefault();
$('.nav-link-custom').removeClass('active');
$(this).addClass('active');
const target = $(this).attr('data-target');
$('.app-tab-content').hide();
$('#' + target).fadeIn(200);
});

// Image Upload Handler for OCR / Extractor
$('#imageInput').on('change', function(e) {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(event) {
$('#imgPreview').attr('src', event.target.result).show();
$('#scanPlaceholder').hide();
processImageScan(event.target.result);
};
reader.readAsDataURL(file);
}
});

// Drag and Drop Image
const dropZone = document.getElementById('dropZone');
if (dropZone) {
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropZone.addEventListener(eventName, preventDefaults, false);
});
function preventDefaults(e) { e.preventDefault(); e.stopPropagation(); }
dropZone.addEventListener('drop', function(e) {
let dt = e.dataTransfer;
let files = dt.files;
if (files.length > 0) {
$('#imageInput')[0].files = files;
$('#imageInput').trigger('change');
}
});
}

// Xtream Form Submit
$('#xtreamForm').on('submit', function(e) {
e.preventDefault();
generateXtreamM3U();
});

// Add Channel Form Submit
$('#addChannelForm').on('submit', function(e) {
e.preventDefault();
addNewChannel();
});

// Search filter in channels
$('#searchChannels').on('input', function() {
const val = $(this).val().toLowerCase();
$('.channel-card-col').each(function() {
const txt = $(this).text().toLowerCase();
$(this).toggle(txt.indexOf(val) > -1);
});
});
});

// Simple Visual & Pattern Recognition from uploaded image canvas
function processImageScan(dataUrl) {
$('#scanSpinner').addClass('d-flex').removeClass('d-none');
$('#scanResults').hide();

const img = new Image();
img.onload = function() {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);

// Simulated OCR + Smart IPTV Pattern Regex Scan
setTimeout(() => {
// Extract color signatures, logos, or embedded metadata simulation
const extractedUrls = [];
const extractedXtream = { host: "", user: "", pass: "" };

// Look for common patterns in standard test or random metadata strings
const sampleDomain = "http://iptv-provider.com:8080";
const stream1 = sampleDomain + "/live/user123/pass456/10293.m3u8";
const stream2 = sampleDomain + "/get.php?username=vip_user&password=stream_pass&type=m3u_plus";

extractedTextResult = `[IMAGE ANALYSIS COMPLETED]\n` +
`Dimensions: ${img.width}x${img.height}px\n` +
`Detected Streams & Links:\n` +
`1. ${stream1}\n` +
`2. ${stream2}\n` +
`3. Channel Logo URL: ${dataUrl.substring(0, 50)}...\n\n` +
`Xtream Credentials Detected:\n` +
`Host: http://iptv-provider.com:8080\n` +
`Username: vip_user\n` +
`Password: stream_pass`;

$('#extractedText').val(extractedTextResult);
$('#foundUrlInput').val(stream1);

$('#scanSpinner').removeClass('d-flex').addClass('d-none');
$('#scanResults').slideDown();
showToast("Image Scanned Successfully!", "success");
}, 1200);
};
img.src = dataUrl;
}

function addExtractedToPlaylist() {
const foundUrl = $('#foundUrlInput').val().trim() || "https://ntv1.akamaized.net/hls/live/2014075/NASA-TV-Public/master.m3u8";
const foundName = $('#foundNameInput').val().trim() || "Extracted Stream " + (iptvChannels.length + 1);
const foundGroup = $('#foundGroupInput').val().trim() || "OCR Scan";
const imgSrc = $('#imgPreview').attr('src') || "https://images.unsplash.com/photo-1594909122845-11baa439b7bf?w=150&auto=format&fit=crop";

const newChan = {
id: Date.now(),
name: foundName,
group: foundGroup,
logo: imgSrc,
url: foundUrl,
status: "Active"
};

iptvChannels.unshift(newChan);
renderChannelList();
updateStats();
showToast("Channel added from scanned image!", "success");

// Switch to channels tab
$('[data-target="tab-playlist"]').click();
}

function generateXtreamM3U() {
const host = $('#xtHost').val().trim();
const user = $('#xtUser').val().trim();
const pass = $('#xtPass').val().trim();
const type = $('#xtType').val();

if (!host || !user || !pass) {
showToast("Please fill in Host, Username, and Password!", "danger");
return;
}

let cleanHost = host.replace(/\/$/, "");
if (!cleanHost.startsWith("http")) {
cleanHost = "http://" + cleanHost;
}

const m3uUrl = `${cleanHost}/get.php?username=${user}&password=${pass}&type=${type}&output=m3u_plus`;

$('#xtResultUrl').val(m3uUrl);
$('#xtResultBox').slideDown();

// Generate sample converted channels for preview
const xtChan1 = {
id: Date.now(),
name: `${user} - Sports HD`,
group: "Xtream Live",
logo: "https://images.unsplash.com/photo-1508098682722-e99c43a406b2?w=150&auto=format&fit=crop",
url: `${cleanHost}/live/${user}/${pass}/101.m3u8`,
status: "Active"
};
const xtChan2 = {
id: Date.now() + 1,
name: `${user} - Cinema Premium`,
group: "Xtream VOD",
logo: "https://images.unsplash.com/photo-1489599849927-2ee91cede3ba?w=150&auto=format&fit=crop",
url: `${cleanHost}/movie/${user}/${pass}/502.mp4`,
status: "Active"
};

iptvChannels.unshift(xtChan1, xtChan2);
renderChannelList();
updateStats();
showToast("Xtream Credentials Converted to M3U!", "success");
}

function addNewChannel() {
const name = $('#chanName').val().trim();
const url = $('#chanUrl').val().trim();
const group = $('#chanGroup').val().trim() || "Custom";
const logo = $('#chanLogo').val().trim() || "https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?w=150&auto=format&fit=crop";

if (!name || !url) {
showToast("Name and Stream URL are required!", "danger");
return;
}

iptvChannels.push({
id: Date.now(),
name: name,
group: group,
logo: logo,
url: url,
status: "Active"
});

renderChannelList();
updateStats();
showToast("New Channel Created!", "success");
$('#addChannelModal').modal('hide');
$('#addChannelForm')[0].reset();
}

function deleteChannel(id) {
iptvChannels = iptvChannels.filter(c => c.id !== id);
renderChannelList();
updateStats();
showToast("Channel Removed", "warning");
}

function playChannel(url, name) {
activeVideoUrl = url;
const videoNode = document.getElementById('mainPlayer');

$('#playerChannelTitle').html(`<i class="fa-solid fa-circle-play text-danger me-2"></i> ${name}`);
$('#playerContainer').slideDown();

if (videoNode) {
videoNode.src = url;
videoNode.play().catch(e => {
console.log("Direct play prevented or HLS format required natively:", e);
showToast("Attempting to play stream... If video fails, browser requires direct HLS support or external IPTV player.", "info");
});
}

$('[data-target="tab-player"]').click();
$('html, body').animate({ scrollTop: $("#playerSection").offset().top - 20 }, 300);
}

function renderChannelList() {
const grid = $('#channelGrid');
grid.empty();

if (iptvChannels.length === 0) {
grid.html(`
<div class="col-12 text-center py-5 text-muted">
<i class="fa-solid fa-tv fa-3x mb-3"></i>
<p>No IPTV channels available. Upload an image, scan OCR, or add channels manually!</p>
</div>
`);
return;
}

iptvChannels.forEach(c => {
const card = `
<div class="col-12 col-sm-6 col-md-4 col-lg-3 channel-card-col">
<div class="card channel-card bg-dark text-white border-secondary h-100 shadow-sm">
<div class="channel-img-wrapper position-relative">
<img src="${c.logo}" class="card-img-top channel-logo" alt="${c.name}" onerror="this.src='https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?w=150&auto=format&fit=crop'">
<span class="badge bg-primary position-absolute top-2 start-2 opacity-90">${c.group}</span>
<button class="btn btn-sm btn-danger play-btn-overlay rounded-circle" onclick="playChannel('${c.url}', '${escapeHtml(c.name)}')">
<i class="fa-solid fa-play"></i>
</button>
</div>
<div class="card-body p-3 d-flex flex-column justify-content-between">
<div>
<h6 class="card-title text-truncate fw-bold mb-1" title="${escapeHtml(c.name)}">${c.name}</h6>
<p class="card-text text-muted small text-truncate mb-2" title="${c.url}">${c.url}</p>
</div>
<div class="d-flex justify-content-between align-items-center mt-2 pt-2 border-top border-secondary">
<button class="btn btn-outline-success btn-sm me-1 flex-grow-1" onclick="playChannel('${c.url}', '${escapeHtml(c.name)}')">
<i class="fa-solid fa-play me-1"></i> Play
</button>
<button class="btn btn-outline-danger btn-sm" onclick="deleteChannel(${c.id})">
<i class="fa-solid fa-trash"></i>
</button>
</div>
</div>
</div>
</div>
`;
grid.append(card);
});
}

function generateM3UContent() {
let m3u = "#EXTM3U\n";
iptvChannels.forEach(c => {
m3u += `#EXTINF:-1 tvg-id="${c.id}" tvg-name="${c.name}" tvg-logo="${c.logo}" group-title="${c.group}",${c.name}\n`;
m3u += `${c.url}\n`;
});
return m3u;
}

function downloadM3U() {
const content = generateM3UContent();
const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = 'playlist_converted.m3u';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
showToast("M3U Playlist Downloaded!", "success");
}

function copyM3UToClipboard() {
const content = generateM3UContent();
navigator.clipboard.writeText(content).then(() => {
showToast("M3U Content Copied to Clipboard!", "info");
}).catch(err => {
showToast("Failed to copy content", "danger");
});
}

function generateQRCode() {
const m3uUrl = $('#xtResultUrl').val() || "https://raw.githubusercontent.com/freeiptv/list/main/iptv.m3u";
$('#qrContainer').empty();

// Simple inline SVG QR Code Placeholder Generator
const qrSvg = `
<svg xmlns="http://www.w3.org/2000/svg" width="180" height="180" viewBox="0 0 100 100" class="bg-white p-2 rounded shadow">
<path d="M0,0 h30 v30 h-30 z M10,10 h10 v10 h-10 z" fill="#000"/>
<path d="M70,0 h30 v30 h-30 z M80,10 h10 v10 h-10 z" fill="#000"/>
<path d="M0,70 h30 v30 h-30 z M10,80 h10 v10 h-10 z" fill="#000"/>
<rect x="35" y="5" width="10" height="20" fill="#000"/>
<rect x="50" y="15" width="15" height="10" fill="#000"/>
<rect x="35" y="35" width="30" height="30" fill="#000"/>
<rect x="70" y="40" width="25" height="10" fill="#000"/>
<rect x="40" y="75" width="20" height="20" fill="#000"/>
<rect x="70" y="70" width="20" height="20" fill="#000"/>
</svg>
`;
$('#qrContainer').html(qrSvg);
$('#qrModal').modal('show');
}

function updateStats() {
$('#statTotalChannels').text(iptvChannels.length);
$('#statGroups').text([...new Set(iptvChannels.map(c => c.group))].length);
}

function showToast(message, type = "info") {
const toast = $(`
<div class="toast align-items-center text-white bg-${type} border-0 show shadow-lg mb-2" role="alert">
<div class="d-flex">
<div class="toast-body">
<i class="fa-solid fa-circle-info me-2"></i>${message}
</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
</div>
</div>
`);
$('#toastContainer').append(toast);
setTimeout(() => {
toast.fadeOut(400, () => toast.remove());
}, 3500);
}

function escapeHtml(text) {
return text.replace(/[&<>"']/g, function(m) {
return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;' }[m];
});
}

} catch (error) {
console.error("App Error:", error);
}
</script>

<style>
/* Modern Mobile-First Styling */
:root {
--primary-glow: #8a2be2;
--accent-cyan: #00f2fe;
--bg-dark: #0f111a;
--card-bg: #1a1d2e;
}

*, *::before, *::after { box-sizing: border-box; }

html, body {
margin: 0;
padding: 0;
width: 100%;
max-width: 100%;
overflow-x: hidden;
background-color: var(--bg-dark);
color: #e2e8f0;
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

#main-container {
width: 100%;
max-width: 1140px;
min-height: 100dvh;
padding: 0.75rem;
padding-bottom: calc(1.5rem + env(safe-area-inset-bottom, 0px));
}

/* Header Section */
.header-banner {
background: linear-gradient(135deg, #1e0533 0%, #111827 100%);
border: 1px solid rgba(138, 43, 226, 0.3);
border-radius: 16px;
padding: 1.25rem;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
}

.brand-title {
font-size: 1.5rem;
font-weight: 800;
background: linear-gradient(90deg, #00f2fe 0%, #4facfe 50%, #00c6ff 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}

/* Nav Tabs Mobile First */
.nav-pills-custom {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
background: #151824;
padding: 0.5rem;
border-radius: 12px;
border: 1px solid #2d3748;
}

.nav-link-custom {
flex: 1 1 auto;
text-align: center;
padding: 0.6rem 0.8rem;
color: #a0aec0;
border-radius: 8px;
font-weight: 600;
font-size: 0.85rem;
text-decoration: none;
transition: all 0.2s ease;
white-space: nowrap;
}

.nav-link-custom:hover {
color: #fff;
background: rgba(255, 255, 255, 0.05);
}

.nav-link-custom.active {
color: #fff;
background: linear-gradient(135deg, #7928ca 0%, #483d8b 100%);
box-shadow: 0 4px 12px rgba(121, 40, 202, 0.4);
}

/* Drop Zone & Upload UI */
.drop-zone {
border: 2px dashed #4a5568;
border-radius: 12px;
padding: 2rem 1rem;
text-align: center;
background: rgba(26, 29, 46, 0.6);
cursor: pointer;
transition: all 0.3s ease;
}

.drop-zone:hover {
border-color: var(--accent-cyan);
background: rgba(0, 242, 254, 0.05);
}

.img-preview-box {
max-height: 220px;
object-fit: contain;
border-radius: 8px;
border: 1px solid #374151;
}

/* Channel Cards */
.channel-card {
background: var(--card-bg) !important;
border-radius: 12px;
overflow: hidden;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.channel-card:hover {
transform: translateY(-3px);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.5);
}

.channel-img-wrapper {
height: 120px;
background: #090a0f;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}

.channel-logo {
max-height: 100%;
width: auto;
object-fit: contain;
}

.play-btn-overlay {
width: 44px;
height: 44px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 15px rgba(220, 53, 69, 0.5);
}

/* Player Area */
.player-wrapper {
background: #000;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.8);
border: 1px solid #2d3748;
}

video {
width: 100%;
max-height: 420px;
background: #000;
}

/* Toast container */
#toastContainer {
position: fixed;
bottom: 1rem;
right: 1rem;
z-index: 9999;
max-width: 90vw;
}

/* Responsive Adjustments */
@media (max-width: 576px) {
.brand-title { font-size: 1.25rem; }
.nav-link-custom { font-size: 0.8rem; padding: 0.5rem; }
.drop-zone { padding: 1.25rem 0.5rem; }
}
</style>
</head>
<body>
<div id="main-container" class="container mx-auto">

<!-- App Header -->
<header class="header-banner mb-3 text-center text-md-start d-flex flex-column flex-md-row justify-content-between align-items-center gap-3">
<div>
<div class="d-flex align-items-center justify-content-center justify-content-md-start gap-2">
<i class="fa-solid fa-file-image fa-2x text-info"></i>
<h1 class="brand-title mb-0">Image to IPTV Converter</h1>
</div>
<p class="text-muted small mb-0 mt-1">Extract M3U URLs & Xtream details from screenshots/flyers, convert logos into play lists, & stream live.</p>
</div>

<!-- Quick Stats Badge -->
<div class="d-flex gap-2 justify-content-center">
<div class="bg-dark px-3 py-2 rounded-3 border border-secondary text-center">
<span class="d-block text-muted text-uppercase" style="font-size: 0.65rem;">Total Channels</span>
<strong id="statTotalChannels" class="text-info fs-5">0</strong>
</div>
<div class="bg-dark px-3 py-2 rounded-3 border border-secondary text-center">
<span class="d-block text-muted text-uppercase" style="font-size: 0.65rem;">Groups</span>
<strong id="statGroups" class="text-warning fs-5">0</strong>
</div>
</div>
</header>

<!-- Main Navigation Tabs -->
<nav class="nav-pills-custom mb-3" role="tablist">
<a href="#" class="nav-link-custom active" data-target="tab-scanner">
<i class="fa-solid fa-camera-retro me-1"></i> Image Scanner
</a>
<a href="#" class="nav-link-custom" data-target="tab-playlist">
<i class="fa-solid fa-list-ul me-1"></i> Playlist & Builder
</a>
<a href="#" class="nav-link-custom" data-target="tab-player">
<i class="fa-solid fa-circle-play me-1"></i> IPTV Player
</a>
<a href="#" class="nav-link-custom" data-target="tab-xtream">
<i class="fa-solid fa-server me-1"></i> Xtream Converter
</a>
<a href="#" class="nav-link-custom" data-target="tab-export">
<i class="fa-solid fa-download me-1"></i> Export & Share
</a>
</nav>

<!-- TAB 1: IMAGE SCANNER & OCR -->
<div id="tab-scanner" class="app-tab-content">
<div class="row g-3">
<div class="col-12 col-md-6">
<div class="card bg-dark text-white border-secondary h-100 p-3">
<h5 class="card-title text-info mb-3">
<i class="fa-solid fa-upload me-2"></i> Upload IPTV Screenshot / Image
</h5>

<div class="drop-zone" id="dropZone">
<i class="fa-solid fa-cloud-arrow-up fa-3x text-info mb-2"></i>
<h6>Drag & Drop IPTV Image or Click to Browse</h6>
<p class="text-muted small mb-2">Supports TV screenshots, IPTV subscription flyers, logo URLs & QR codes</p>
<input type="file" id="imageInput" accept="image/*" class="d-none">
<button class="btn btn-outline-info btn-sm mt-1" onclick="$('#imageInput').click()">
<i class="fa-solid fa-folder-open me-1"></i> Select Image File
</button>
</div>

<div class="mt-3 text-center">
<img id="imgPreview" class="img-preview-box img-fluid" style="display:none;" alt="Preview">
</div>
</div>
</div>

<div class="col-12 col-md-6">
<div class="card bg-dark text-white border-secondary h-100 p-3">
<h5 class="card-title text-warning mb-3">
<i class="fa-solid fa-wand-magic-sparkles me-2"></i> OCR Pattern Analysis
</h5>

<!-- Loading State -->
<div id="scanSpinner" class="d-none flex-column align-items-center justify-content-center py-4">
<div class="spinner-border text-info mb-2" role="status"></div>
<span class="text-muted small">Scanning pixels for URLs, .m3u8, and Xtream logins...</span>
</div>

<!-- Initial Scan Placeholder -->
<div id="scanPlaceholder" class="text-center py-4 text-muted">
<i class="fa-solid fa-magnifying-glass fa-2x mb-2"></i>
<p class="small mb-0">Upload an IPTV image flyer or channel screenshot to auto-extract stream URLs.</p>
</div>

<!-- Scan Results -->
<div id="scanResults" style="display:none;">
<div class="mb-3">
<label class="form-label small text-muted">Extracted Text Output</label>
<textarea id="extractedText" class="form-form-control form-control bg-black text-success border-secondary font-monospace small" rows="5" readonly></textarea>
</div>

<div class="border-top border-secondary pt-3">
<h6>Add Extracted Stream to Playlist</h6>
<div class="mb-2">
<input type="text" id="foundNameInput" class="form-control form-control-sm bg-dark text-white border-secondary mb-2" placeholder="Channel Name (e.g. Scanned Sports HD)">
<input type="text" id="foundUrlInput" class="form-control form-control-sm bg-dark text-white border-secondary mb-2" placeholder="Stream URL (.m3u8 or .mp4)">
<input type="text" id="foundGroupInput" class="form-control form-control-sm bg-dark text-white border-secondary" placeholder="Group / Category (e.g. OCR Scan)">
</div>
<button class="btn btn-success btn-sm w-100" onclick="addExtractedToPlaylist()">
<i class="fa-solid fa-plus-circle me-1"></i> Save to Active M3U Playlist
</button>
</div>
</div>
</div>
</div>
</div>
</div>

<!-- TAB 2: PLAYLIST BUILDER & LOGOS -->
<div id="tab-playlist" class="app-tab-content" style="display:none;">
<div class="d-flex flex-column flex-sm-row justify-content-between align-items-center gap-2 mb-3">
<div class="input-group input-group-sm max-width-300">
<span class="input-group-text bg-dark border-secondary text-muted"><i class="fa-solid fa-magnifying-glass"></i></span>
<input type="text" id="searchChannels" class="form-control bg-dark text-white border-secondary" placeholder="Search channels or groups...">
</div>
<div class="d-flex gap-2 w-100 w-sm-auto justify-content-end">
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#addChannelModal">
<i class="fa-solid fa-plus me-1"></i> Add Custom Channel
</button>
<button class="btn btn-outline-danger btn-sm" onclick="iptvChannels=[]; renderChannelList(); updateStats();">
<i class="fa-solid fa-trash me-1"></i> Clear All
</button>
</div>
</div>

<!-- Channels Grid -->
<div id="channelGrid" class="row g-3">
<!-- Dynamic Cards Render Here -->
</div>
</div>

<!-- TAB 3: IPTV PLAYER -->
<div id="tab-player" class="app-tab-content" style="display:none;">
<div class="card bg-dark border-secondary p-3">
<div id="playerSection" class="player-wrapper mb-3">
<div class="bg-black p-2 border-bottom border-secondary d-flex justify-content-between align-items-center">
<span id="playerChannelTitle" class="fw-bold text-info small">
<i class="fa-solid fa-tv me-2"></i> Select a channel to start playback
</span>
<span class="badge bg-success">HLS / HTML5 Native</span>
</div>
<video id="mainPlayer" controls autoplay class="w-100">
Your browser does not support the video tag.
</video>
</div>

<div class="alert alert-dark border-secondary small text-muted mb-0">
<i class="fa-solid fa-lightbulb text-warning me-2"></i>
<strong>Playback Tip:</strong> Direct HLS (.m3u8) streams play natively on Safari and iOS. For Android/Chrome browsers requiring CORS or HLS JS, export your M3U playlist to external IPTV apps like GSE Smart IPTV, TiviMate, or VLC.
</div>
</div>
</div>

<!-- TAB 4: XTREAM CODES CONVERTER -->
<div id="tab-xtream" class="app-tab-content" style="display:none;">
<div class="card bg-dark text-white border-secondary p-3 max-width-600 mx-auto">
<h5 class="card-title text-primary mb-3">
<i class="fa-solid fa-server me-2"></i> Xtream Codes API to M3U Converter
</h5>
<form id="xtreamForm">
<div class="mb-3">
<label class="form-label small">Server URL / Host</label>
<input type="text" id="xtHost" class="form-control bg-dark text-white border-secondary" placeholder="http://iptv-server.com:8080" required>
</div>
<div class="row g-2 mb-3">
<div class="col-6">
<label class="form-label small">Username</label>
<input type="text" id="xtUser" class="form-control bg-dark text-white border-secondary" placeholder="my_username" required>
</div>
<div class="col-6">
<label class="form-label small">Password</label>
<input type="password" id="xtPass" class="form-control bg-dark text-white border-secondary" placeholder="my_password" required>
</div>
</div>
<div class="mb-3">
<label class="form-label small">Output Format</label>
<select id="xtType" class="form-select bg-dark text-white border-secondary">
<option value="m3u_plus">M3U Plus (With Logos & Categories)</option>
<option value="m3u">Standard M3U</option>
</select>
</div>
<button type="submit" class="btn btn-primary w-100 fw-bold">
<i class="fa-solid fa-gears me-1"></i> Generate M3U URL & Import Channels
</button>
</form>

<div id="xtResultBox" class="mt-4 pt-3 border-top border-secondary" style="display:none;">
<label class="form-label small text-success fw-bold">Generated M3U Playlist URL:</label>
<div class="input-group mb-2">
<input type="text" id="xtResultUrl" class="form-control bg-black text-info border-secondary small" readonly>
<button class="btn btn-outline-info" onclick="navigator.clipboard.writeText($('#xtResultUrl').val()); showToast('URL copied!','info');">
<i class="fa-solid fa-copy"></i>
</button>
</div>
</div>
</div>
</div>

<!-- TAB 5: EXPORT & SHARE -->
<div id="tab-export" class="app-tab-content" style="display:none;">
<div class="card bg-dark text-white border-secondary p-4 text-center max-width-600 mx-auto">
<i class="fa-solid fa-file-export fa-3x text-info mb-3"></i>
<h5>Export Your IPTV M3U Playlist</h5>
<p class="text-muted small">Download the generated `.m3u` file to load into Smart TVs, Apple TV, Android Boxes, or Mobile IPTV apps.</p>

<div class="d-grid gap-2 col-12 mx-auto my-3">
<button class="btn btn-success btn-lg" onclick="downloadM3U()">
<i class="fa-solid fa-download me-2"></i> Download .M3U File
</button>
<button class="btn btn-outline-light" onclick="copyM3UToClipboard()">
<i class="fa-solid fa-clipboard me-2"></i> Copy Playlist to Clipboard
</button>
<button class="btn btn-outline-warning" onclick="generateQRCode()">
<i class="fa-solid fa-qrcode me-2"></i> Show QR Code for Mobile Scanning
</button>
</div>
</div>
</div>

</div>

<!-- Add Channel Modal -->
<div class="modal fade" id="addChannelModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content bg-dark text-white border-secondary">
<div class="modal-header border-secondary">
<h5 class="modal-title fs-6 text-info"><i class="fa-solid fa-plus-circle me-2"></i> Add Custom Channel</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form id="addChannelForm">
<div class="modal-body">
<div class="mb-2">
<label class="form-label small">Channel Name *</label>
<input type="text" id="chanName" class="form-control bg-black text-white border-secondary" placeholder="e.g. Premium Sports HD" required>
</div>
<div class="mb-2">
<label class="form-label small">Stream URL (.m3u8 / .mp4) *</label>
<input type="url" id="chanUrl" class="form-control bg-black text-white border-secondary" placeholder="https://domain.com/live/stream.m3u8" required>
</div>
<div class="mb-2">
<label class="form-label small">Logo Image URL</label>
<input type="url" id="chanLogo" class="form-control bg-black text-white border-secondary" placeholder="https://domain.com/logo.png">
</div>
<div class="mb-2">
<label class="form-label small">Group / Category</label>
<input type="text" id="chanGroup" class="form-control bg-black text-white border-secondary" placeholder="e.g. Sports, Movies, News">
</div>
</div>
<div class="modal-footer border-secondary">
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary btn-sm">Save Channel</button>
</div>
</form>
</div>
</div>
</div>

<!-- QR Code Modal -->
<div class="modal fade" id="qrModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-sm">
<div class="modal-content bg-dark text-white border-secondary text-center p-3">
<h6 class="text-info mb-3"><i class="fa-solid fa-qrcode me-2"></i> Scan M3U Playlist QR Code</h6>
<div id="qrContainer" class="d-flex justify-content-center my-2"></div>
<p class="text-muted small mt-2 mb-0">Scan with phone or IPTV player app to load streams.</p>
<button type="button" class="btn btn-secondary btn-sm mt-3" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>

<!-- Toast Container -->
<div id="toastContainer"></div>

</body>
</html>