kinostream4/asil-tv
0
1<!DOCTYPE html>2<html lang="uz">3<head>4<meta charset="UTF-8">5<meta name="viewport" content="width=device-width, initial-scale=1.0">6<title>Universal IPTV Editor - Group Logo Support</title>7<style>8 :root {9 --bg-color: #121212;10 --card-bg: #1e1e1e;11 --text-main: #e0e0e0;12 --text-muted: #a0a0a0;13 --accent: #bb86fc;14 --accent-hover: #9965f4;15 --success: #03dac6;16 --danger: #cf6679;17 --border: #333;18 }19 20 * { margin: 0; padding: 0; box-sizing: border-box; outline: none; }21 22 body {23 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;24 background-color: var(--bg-color);25 color: var(--text-main);26 min-height: 100vh;27 display: flex;28 justify-content: center;29 padding: 20px;30 }31 32 .container {33 width: 100%;34 max-width: 1100px;35 background: var(--card-bg);36 border-radius: 16px;37 box-shadow: 0 10px 30px rgba(0,0,0,0.5);38 overflow: hidden;39 border: 1px solid var(--border);40 }41 42 .header {43 background: linear-gradient(135deg, #3700b3, #6200ee);44 padding: 20px;45 text-align: center;46 color: white;47 }48 49 .content { padding: 20px; }50 51 .upload-area {52 border: 2px dashed var(--accent);53 border-radius: 12px;54 padding: 30px;55 text-align: center;56 cursor: pointer;57 background: rgba(187, 134, 252, 0.05);58 margin-bottom: 20px;59 transition: 0.3s;60 }61 .upload-area:hover { background: rgba(187, 134, 252, 0.1); border-color: var(--accent-hover); }62 63 .top-controls {64 display: flex;65 gap: 10px;66 margin-bottom: 20px;67 flex-wrap: wrap;68 }69 70 .btn {71 border: none;72 padding: 10px 20px;73 border-radius: 8px;74 cursor: pointer;75 font-weight: bold;76 transition: 0.2s;77 display: inline-flex;78 align-items: center;79 gap: 5px;80 }81 .btn-primary { background: var(--accent); color: #000; }82 .btn-success { background: var(--success); color: #000; }83 .btn-danger { background: var(--danger); color: #fff; }84 .btn-sort { background: #ff9800; color: #000; }85 86 .preview-box {87 max-height: 65vh;88 overflow-y: auto;89 border: 1px solid var(--border);90 border-radius: 8px;91 background: #181818;92 }93 94 /* Scrollbar */95 .preview-box::-webkit-scrollbar { width: 8px; }96 .preview-box::-webkit-scrollbar-track { background: #121212; }97 .preview-box::-webkit-scrollbar-thumb { background: #444; border-radius: 4px; }98 99 .group-card { 100 border-bottom: 1px solid var(--border); 101 background: #222;102 }103 104 .group-header {105 padding: 15px;106 background: #2a2a2a;107 display: flex;108 align-items: center;109 gap: 15px;110 position: sticky;111 top: 0;112 z-index: 10;113 border-left: 4px solid var(--accent);114 }115 116 /* Group Logo Style */117 .group-logo-img {118 width: 50px;119 height: 50px;120 object-fit: contain;121 background: #000;122 border-radius: 8px;123 border: 1px solid #555;124 }125 126 .group-title { 127 flex: 1; 128 font-weight: bold; 129 color: var(--accent); 130 display: flex; 131 justify-content: space-between; 132 align-items: center;133 font-size: 1.1rem;134 }135 136 .action-btn {137 width: 32px; height: 32px;138 border-radius: 50%; border: none;139 cursor: pointer;140 display: flex; align-items: center; justify-content: center;141 color: white; margin-left: 5px;142 font-size: 14px;143 transition: transform 0.2s;144 }145 .action-btn:hover { transform: scale(1.1); }146 147 .btn-move-up { background: #ff9800; }148 .btn-move-down { background: #2196f3; }149 .btn-edit { background: #9c27b0; }150 .btn-toggle { background: #607d8b; }151 .btn-group-logo { background: #e91e63; } /* Pink for Group Logo */152 153 .channel-row {154 display: flex;155 align-items: center;156 padding: 8px 15px;157 border-top: 1px solid #333;158 transition: background 0.2s;159 }160 .channel-row:hover { background: #333; }161 162 /* Channel Logo Image Style */163 .ch-logo-img {164 width: 40px;165 height: 40px;166 object-fit: contain;167 background: #000;168 border-radius: 4px;169 margin-right: 10px;170 border: 1px solid #444;171 }172 173 .channel-info { flex: 1; overflow: hidden; }174 .ch-name { font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-bottom: 2px;}175 .ch-url { font-size: 0.7rem; color: var(--text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }176 177 .ch-actions { display: flex; gap: 5px; margin-left: 5px; }178 179 .ch-move-btn, .ch-logo-btn {180 width: 28px; height: 28px;181 background: #333; color: #ccc;182 border-radius: 4px; border: none;183 cursor: pointer;184 display: flex; align-items: center; justify-content: center;185 font-size: 12px;186 }187 .ch-move-btn:hover { background: var(--accent); color: #000; }188 .ch-logo-btn:hover { background: #00bcd4; color: #fff; } 189 190 .toast {191 position: fixed; bottom: 20px; left: 50%;192 transform: translateX(-50%);193 background: var(--success); color: #000;194 padding: 12px 24px; border-radius: 30px;195 font-weight: bold; z-index: 1000;196 box-shadow: 0 4px 12px rgba(0,0,0,0.3);197 }198</style>199</head>200<body>201 202<div class="container">203 <div class="header">204 <h1>Universal IPTV Editor PRO</h1>205 <p>Guruh va Kanal Logolari + Aqlli Saralash</p>206 </div>207 208 <div class="content">209 <div class="upload-area" id="uploadArea">210 <span style="font-size: 40px;">๐</span>211 <h3>M3U faylni tashlang</h3>212 <input type="file" id="fileInput" multiple accept=".m3u,.m3u8" hidden>213 </div>214 215 <div class="top-controls">216 <button class="btn btn-primary" id="selectAllBtn">โ
Hammasi</button>217 <button class="btn btn-danger" id="deselectAllBtn">โ Tozalash</button>218 <button class="btn btn-sort" id="autoSortBtn">๐ข Avto-Saralash</button>219 <button class="btn btn-success" id="saveBtn"> Saqlash (M3U)</button>220 </div>221 222 <div id="preview" class="preview-box"></div>223 </div>224</div>225 226<script>227class IPTVEditor {228 constructor() {229 this.channels = [];230 this.selectedIds = new Set();231 this.groupRenameMap = {};232 // Guruh logolarini saqlash uchun obyekt233 this.groupLogos = {}; 234 this.init();235 }236 237 init() {238 const uploadArea = document.getElementById('uploadArea');239 const fileInput = document.getElementById('fileInput');240 241 uploadArea.onclick = () => fileInput.click();242 fileInput.addEventListener('change', (e) => this.loadFiles(e.target.files));243 244 document.getElementById('selectAllBtn').onclick = () => {245 this.channels.forEach(ch => this.selectedIds.add(ch.id));246 this.render();247 };248 249 document.getElementById('deselectAllBtn').onclick = () => {250 this.selectedIds.clear();251 this.render();252 };253 254 document.getElementById('saveBtn').onclick = () => this.exportM3U();255 document.getElementById('autoSortBtn').onclick = () => this.smartSortAll();256 }257 258 loadFiles(files) {259 if (!files.length) return;260 this.channels = [];261 this.groupLogos = {}; // Yangi fayl yuklanganda logolarni tozalash262 const reader = new FileReader();263 reader.onload = (e) => {264 this.parseM3U(e.target.result);265 this.removeDuplicates();266 this.render();267 this.showMessage('โ
Fayl yuklandi!');268 };269 reader.readAsText(files[0], 'utf-8');270 }271 272 parseM3U(content) {273 const lines = content.split(/\r?\n/);274 let extinf = null;275 276 for (let line of lines) {277 line = line.trim();278 if (line.startsWith('#EXTINF:')) {279 extinf = line;280 } else if (line && !line.startsWith('#') && extinf) {281 const name = extinf.split(',').pop().trim();282 let group = 'Boshqa';283 let logo = '';284 285 const grp = extinf.match(/group-title="([^"]+)"/);286 if (grp) group = grp[1];287 288 const lg = extinf.match(/tvg-logo="([^"]+)"/);289 if (lg) logo = lg[1];290 291 group = group.replace(/[๐]/g, '').trim();292 293 this.channels.push({294 id: 'ch_' + Date.now() + '_' + Math.random(),295 name,296 group,297 logo,298 url: line299 });300 extinf = null;301 }302 }303 }304 305 removeDuplicates() {306 const unique = new Map();307 this.channels.forEach(ch => {308 const key = ch.name.toLowerCase() + '|' + ch.url.toLowerCase();309 if (!unique.has(key)) unique.set(key, ch);310 });311 this.channels = Array.from(unique.values());312 }313 314 // --- SMART SORT ---315 extractNumber(str) {316 const match = str.match(/\d+/);317 return match ? parseInt(match[0], 10) : null;318 }319 320 smartSortAll() {321 const groupsMap = {};322 const groupOrder = [];323 324 this.channels.forEach(ch => {325 const g = this.groupRenameMap[ch.group] || ch.group;326 if (!groupsMap[g]) {327 groupsMap[g] = [];328 groupOrder.push(g);329 }330 groupsMap[g].push(ch);331 });332 333 let newChannels = [];334 335 groupOrder.forEach(gName => {336 const groupChannels = groupsMap[gName];337 groupChannels.sort((a, b) => {338 const numA = this.extractNumber(a.name);339 const numB = this.extractNumber(b.name);340 if (numA !== null && numB !== null) return numA - numB;341 if (numA !== null) return -1;342 if (numB !== null) return 1;343 return a.name.localeCompare(b.name);344 });345 newChannels = newChannels.concat(groupChannels);346 });347 348 this.channels = newChannels;349 this.render();350 this.showMessage(' Kanallar 1, 2, 3 tartibida saralandi!');351 }352 353 // --- MOVE LOGIC ---354 moveGroupUp(groupName) { this.reorderGroups(groupName, -1); }355 moveGroupDown(groupName) { this.reorderGroups(groupName, 1); }356 357 reorderGroups(targetGroup, direction) {358 let currentOrder = [];359 let seen = new Set();360 this.channels.forEach(ch => {361 let g = this.groupRenameMap[ch.group] || ch.group;362 if (!seen.has(g)) { currentOrder.push(g); seen.add(g); }363 });364 365 const index = currentOrder.indexOf(targetGroup);366 if (index < 0) return;367 const newIndex = index + direction;368 if (newIndex < 0 || newIndex >= currentOrder.length) return;369 370 [currentOrder[index], currentOrder[newIndex]] = [currentOrder[newIndex], currentOrder[index]];371 372 let newChannels = [];373 currentOrder.forEach(gName => {374 this.channels.forEach(ch => {375 let chGroup = this.groupRenameMap[ch.group] || ch.group;376 if (chGroup === gName) newChannels.push(ch);377 });378 });379 380 this.channels = newChannels;381 this.render();382 }383 384 moveChannelUp(channelId) { this.moveChannel(channelId, -1); }385 moveChannelDown(channelId) { this.moveChannel(channelId, 1); }386 387 moveChannel(targetId, direction) {388 const targetIndex = this.channels.findIndex(ch => ch.id === targetId);389 if (targetIndex < 0) return;390 const newIndex = targetIndex + direction;391 if (newIndex < 0 || newIndex >= this.channels.length) return;392 393 const targetGroup = this.groupRenameMap[this.channels[targetIndex].group] || this.channels[targetIndex].group;394 const neighborGroup = this.groupRenameMap[this.channels[newIndex].group] || this.channels[newIndex].group;395 396 if (targetGroup !== neighborGroup) return;397 398 [this.channels[targetIndex], this.channels[newIndex]] = [this.channels[newIndex], this.channels[targetIndex]];399 this.render();400 }401 402 // --- LOGO EDIT FUNCTIONS ---403 404 // Kanal logosini o'zgartirish405 editChannelLogo(channelId) {406 const channel = this.channels.find(ch => ch.id === channelId);407 if (!channel) return;408 409 const newLogo = prompt("Kanal uchun yangi Logo URL:", channel.logo);410 if (newLogo !== null) {411 channel.logo = newLogo.trim();412 this.render();413 this.showMessage('๏ธ Kanal logosi yangilandi!');414 }415 }416 417 // Guruh logosini o'zgartirish418 editGroupLogo(groupName) {419 // Hozirgi nomni tekshirish (agar rename bo'lgan bo'lsa)420 const realGroupName = Object.keys(this.groupRenameMap).find(key => this.groupRenameMap[key] === groupName) || groupName;421 422 const currentLogo = this.groupLogos[realGroupName] || '';423 const newLogo = prompt(`"${groupName}" guruhi uchun Logo URL:`, currentLogo);424 425 if (newLogo !== null) {426 this.groupLogos[realGroupName] = newLogo.trim();427 428 // Ixtiyoriy: Barcha kanallarga ham shu logoni qo'yishmi?429 const applyToAll = confirm("Bu logoni ushbu guruhdagi BARCHA kanallarga ham qo'yilsinmi?");430 431 if (applyToAll) {432 this.channels.forEach(ch => {433 let chGroup = this.groupRenameMap[ch.group] || ch.group;434 if (chGroup === groupName) {435 ch.logo = newLogo.trim();436 }437 });438 }439 440 this.render();441 this.showMessage(' Guruh logosi yangilandi!');442 }443 }444 445 // --- RENDER ---446 render() {447 const preview = document.getElementById('preview');448 preview.innerHTML = '';449 450 const groupsMap = {};451 const groupOrder = [];452 453 this.channels.forEach(ch => {454 const g = this.groupRenameMap[ch.group] || ch.group;455 if (!groupsMap[g]) {456 groupsMap[g] = [];457 groupOrder.push(g);458 }459 groupsMap[g].push(ch);460 });461 462 if (groupOrder.length === 0) {463 preview.innerHTML = '<div style="padding:20px; text-align:center; color:#666;">Hali hech nima yuklanmagan</div>';464 return;465 }466 467 groupOrder.forEach(groupName => {468 const channels = groupsMap[groupName];469 470 const groupDiv = document.createElement('div');471 groupDiv.className = 'group-card';472 473 const header = document.createElement('div');474 header.className = 'group-header';475 476 const cb = document.createElement('input');477 cb.type = 'checkbox';478 cb.checked = channels.every(ch => this.selectedIds.has(ch.id));479 cb.onchange = () => {480 if(cb.checked) channels.forEach(c => this.selectedIds.add(c.id));481 else channels.forEach(c => this.selectedIds.delete(c.id));482 this.render();483 };484 485 // Guruh Logosi486 // Asl guruh nomini topish (rename map orqali)487 const originalGroupName = Object.keys(this.groupRenameMap).find(key => this.groupRenameMap[key] === groupName) || groupName;488 const groupLogoUrl = this.groupLogos[originalGroupName] || 'https://via.placeholder.com/50x50/333/fff?text=Grp';489 490 const groupLogoImg = document.createElement('img');491 groupLogoImg.className = 'group-logo-img';492 groupLogoImg.src = groupLogoUrl;493 groupLogoImg.onerror = function() { this.src = 'https://via.placeholder.com/50x50/333/fff?text=Err'; };494 495 const titleWrap = document.createElement('div');496 titleWrap.className = 'group-title';497 titleWrap.innerHTML = `<span>${groupName} <span style="background:#333; padding:2px 8px; border-radius:10px; font-size:0.8em; color:#fff;">${channels.length}</span></span>`;498 499 const btnsDiv = document.createElement('div');500 btnsDiv.style.display = 'flex';501 btnsDiv.style.gap = '5px';502 503 const createBtn = (text, cls, onClick, title='') => {504 const btn = document.createElement('button');505 btn.className = `action-btn ${cls}`;506 btn.innerText = text;507 btn.title = title;508 btn.onclick = onClick;509 return btn;510 };511 512 btnsDiv.append(513 createBtn('โฌ๏ธ', 'btn-move-up', () => this.moveGroupUp(groupName), "Guruhni tepaga"),514 createBtn('โฌ๏ธ', 'btn-move-down', () => this.moveGroupDown(groupName), "Guruhni pastga"),515 createBtn('๐ผ๏ธ', 'btn-group-logo', () => this.editGroupLogo(groupName), "Guruh logosini o'zgartirish"),516 createBtn('๐๏ธ', 'btn-toggle', () => {517 listDiv.style.display = listDiv.style.display === 'none' ? 'block' : 'none';518 }, "Yashirish/Ko'rsatish"),519 createBtn('โ๏ธ', 'btn-edit', () => {520 const newName = prompt("Guruh nomini o'zgartirish:", groupName);521 if(newName && newName.trim()) {522 channels.forEach(c => c.group = newName);523 this.groupRenameMap[groupName] = newName;524 this.render();525 }526 }, "Nomini o'zgartirish")527 );528 529 titleWrap.append(btnsDiv);530 header.append(cb, groupLogoImg, titleWrap);531 groupDiv.append(header);532 533 const listDiv = document.createElement('div');534 listDiv.className = 'channel-list';535 listDiv.style.display = 'block'; 536 537 channels.forEach(ch => {538 const row = document.createElement('div');539 row.className = 'channel-row';540 541 const chCb = document.createElement('input');542 chCb.type = 'checkbox';543 chCb.checked = this.selectedIds.has(ch.id);544 chCb.onchange = () => {545 if(chCb.checked) this.selectedIds.add(ch.id);546 else this.selectedIds.delete(ch.id);547 };548 549 // Kanal Logosi550 const logoImg = document.createElement('img');551 logoImg.className = 'ch-logo-img';552 logoImg.src = ch.logo ? ch.logo : 'https://via.placeholder.com/40x40/333/fff?text=No+Logo';553 logoImg.onerror = function() { this.src = 'https://via.placeholder.com/40x40/333/fff?text=Err'; };554 555 const info = document.createElement('div');556 info.className = 'channel-info';557 info.innerHTML = `<div class="ch-name">๐บ ${ch.name}</div><div class="ch-url">${ch.url}</div>`;558 559 const chActions = document.createElement('div');560 chActions.className = 'ch-actions';561 562 const chUp = document.createElement('button');563 chUp.className = 'ch-move-btn';564 chUp.innerText = '';565 chUp.title = "Tepaga";566 chUp.onclick = () => this.moveChannelUp(ch.id);567 568 const chDown = document.createElement('button');569 chDown.className = 'ch-move-btn';570 chDown.innerText = 'โฌ';571 chDown.title = "Pastga";572 chDown.onclick = () => this.moveChannelDown(ch.id);573 574 const chLogoBtn = document.createElement('button');575 chLogoBtn.className = 'ch-logo-btn';576 chLogoBtn.innerText = '๐ผ๏ธ';577 chLogoBtn.title = "Kanal logosini o'zgartirish";578 chLogoBtn.onclick = () => this.editChannelLogo(ch.id);579 580 chActions.append(chUp, chDown, chLogoBtn);581 582 row.append(chCb, logoImg, info, chActions);583 listDiv.append(row);584 });585 586 groupDiv.append(listDiv);587 preview.append(groupDiv);588 });589 }590 591 exportM3U() {592 const selected = this.channels.filter(ch => this.selectedIds.has(ch.id));593 if (!selected.length) {594 this.showMessage('โ Hech nima tanlanmagan!');595 return;596 }597 598 let m3u = '#EXTM3U\n';599 selected.forEach(ch => {600 let line = `#EXTINF:-1 group-title="${ch.group}"`;601 if (ch.logo) line += ` tvg-logo="${ch.logo}"`;602 line += `,${ch.name}\n${ch.url}\n`;603 m3u += line;604 });605 606 const blob = new Blob([m3u], { type: 'application/x-mpegURL' });607 const url = URL.createObjectURL(blob);608 const a = document.createElement('a');609 a.href = url;610 a.download = 'final_playlist_with_group_logos.m3u';611 a.click();612 URL.revokeObjectURL(url);613 this.showMessage(`โ
Saqlandi: ${selected.length} ta kanal`);614 }615 616 showMessage(msg) {617 const toast = document.createElement('div');618 toast.className = 'toast';619 toast.innerText = msg;620 document.body.appendChild(toast);621 setTimeout(() => toast.remove(), 3000);622 }623}624 625new IPTVEditor();626</script>627 628</body>629</html>