const fs = require('fs'); const path = require('path'); // Define cultural groups by country with more precise matching const culturalGroups = { anglosphere: [ 'united kingdom', 'uk', 'great britain', 'britain', 'england', 'scotland', 'wales', 'northern ireland', '^united states$', '^usa$', '^us$', '^america$', '^canada$', '^australia$', '^new zealand$', '^ireland$' ], francophone: [ '^france$', '^belgium$', '^switzerland$', '^quebec$', '^monaco$', '^luxembourg$', '^haiti$', '^ivory coast$', '^senegal$', '^cameroon$' ], hispanic: [ '^spain$', '^mexico$', '^argentina$', '^chile$', '^colombia$', '^peru$', '^venezuela$', '^ecuador$', '^guatemala$', '^cuba$', '^dominican republic$', '^honduras$', '^el salvador$', '^nicaragua$', '^costa rica$', '^panama$', '^bolivia$', '^paraguay$', '^uruguay$', 'latin america' ], lusophone: [ '^portugal$', '^brazil$', '^angola$', '^mozambique$', '^cape verde$', '^guinea-bissau$', '^sao tome and principe$' ], arabic: [ 'saudi arabia', 'egypt', 'uae', 'united arab emirates', 'qatar', 'kuwait', 'oman', 'bahrain', 'yemen', 'iraq', 'syria', 'jordan', 'lebanon', 'palestine', 'libya', 'tunisia', 'algeria', 'morocco', 'sudan' ], germanosphere: [ '^germany$', '^austria$', '^switzerland$', '^luxembourg$', '^liechtenstein$' ], slavic: [ '^russia$', '^ukraine$', '^belarus$', '^poland$', '^czech republic$', '^slovakia$', '^serbia$', '^croatia$', '^bosnia$', '^montenegro$', '^slovenia$', '^bulgaria$', '^north macedonia$' ], sinosphere: [ '^china$', 'hong kong', '^taiwan$', '^singapore$', '^macau$' ], indosphere: [ '^india$', '^pakistan$', '^bangladesh$', '^nepal$', '^sri lanka$', '^bhutan$', '^maldives$' ], turkic: [ '^turkey$', '^azerbaijan$', '^uzbekistan$', '^kazakhstan$', '^kyrgyzstan$', '^turkmenistan$' ], nordic: [ '^sweden$', '^norway$', '^denmark$', '^finland$', '^iceland$', 'faroe islands', '^greenland$' ], baltic: [ '^estonia$', '^latvia$', '^lithuania$' ], hellenic: [ '^greece$', '^cyprus$' ], benelux: [ '^netherlands$', '^belgium$', '^luxembourg$' ], persian: [ '^iran$', '^afghanistan$', '^tajikistan$' ], malaysphere: [ '^malaysia$', '^brunei$', '^indonesia$' ], korean: [ 'south korea', 'korea', 'north korea' ], japanese: [ '^japan$' ], vietnamese: [ '^vietnam$' ], thai: [ '^thailand$' ] }; function getCulturalGroup(channelInfo) { // Look specifically for group-title (country information) const groupMatch = channelInfo.match(/group-title="([^"]*)"/) || []; const groupTitle = (groupMatch[1] || '').toLowerCase(); // Check if the country belongs to any cultural group for (const [group, countries] of Object.entries(culturalGroups)) { // Use exact matching with RegExp if (countries.some(country => { // If the country pattern starts with ^, use it as a RegExp if (country.startsWith('^')) { const regex = new RegExp(country, 'i'); return regex.test(groupTitle); } // Otherwise, use includes for flexible matching (for multi-word countries) return groupTitle.includes(country); })) { return group; } } return null; } function splitByCulturalGroup(filePath) { const content = fs.readFileSync(filePath, 'utf8'); const lines = content.split('\n'); // Create cultural groups directory const groupsDir = path.join(path.dirname(filePath), 'cultural-groups'); if (!fs.existsSync(groupsDir)) { fs.mkdirSync(groupsDir); } // Get existing cultural group files const existingFiles = fs.readdirSync(groupsDir) .filter(file => file.endsWith('.m3u')) .map(file => file.toLowerCase()); const groups = {}; // Verify M3U header const header = lines[0]; if (!header.startsWith('#EXTM3U')) { throw new Error('Invalid M3U file: Missing #EXTM3U header'); } // Process the file line by line to handle multi-line entries let currentCulturalGroup = null; let currentEntry = []; let isInEntry = false; for (let i = 1; i < lines.length; i++) { const line = lines[i].trim(); if (!line) continue; if (line.startsWith('#EXTINF')) { // Start of a new entry isInEntry = true; currentEntry = [line]; // Determine cultural group currentCulturalGroup = getCulturalGroup(line); } else if (isInEntry) { // Add the line to the current entry currentEntry.push(line); // If this is a URL line (doesn't start with #), this completes the entry if (!line.startsWith('#')) { // Add all lines of the entry to the appropriate cultural group if (currentCulturalGroup) { if (!groups[currentCulturalGroup]) { groups[currentCulturalGroup] = ['#EXTM3U']; } groups[currentCulturalGroup].push(...currentEntry); } // Reset for the next entry isInEntry = false; currentEntry = []; currentCulturalGroup = null; } } } // Get list of current cultural group files const currentGroupFiles = Object.keys(groups).map(groupTitle => `${groupTitle.toLowerCase()}.m3u` ); // Remove obsolete files existingFiles.forEach(existingFile => { if (!currentGroupFiles.includes(existingFile)) { const fileToRemove = path.join(groupsDir, existingFile); fs.unlinkSync(fileToRemove); console.log(`Removed obsolete cultural group playlist: ${existingFile}`); } }); // Write cultural group files Object.entries(groups).forEach(([groupTitle, groupLines]) => { const groupFilePath = path.join(groupsDir, `${groupTitle.toLowerCase()}.m3u`); fs.writeFileSync(groupFilePath, groupLines.join('\n') + '\n'); console.log(`Created/updated cultural group playlist: ${groupFilePath}`); }); // Generate summary - count entries properly by counting #EXTINF lines const summary = Object.entries(groups).map(([group, lines]) => { const channelCount = lines.filter(line => line.startsWith('#EXTINF')).length; return `${group}: ${channelCount} channels`; }); console.log('\nCultural group split summary:'); console.log(summary.join('\n')); } const filePath = process.argv[2]; if (!filePath) { console.error('Please provide the path to the M3U file'); process.exit(1); } try { splitByCulturalGroup(filePath); } catch (error) { console.error('Error splitting M3U file:', error.message); process.exit(1); }