From 68c8d4dc234d478e173143b191d306a4c981eb27 Mon Sep 17 00:00:00 2001 From: Connor Smith Date: Fri, 26 Sep 2025 21:41:48 -0400 Subject: [PATCH 1/7] Change logo upload to link to Logo.jsx form --- frontend/src/components/forms/Channel.jsx | 78 ++++++----------------- 1 file changed, 21 insertions(+), 57 deletions(-) diff --git a/frontend/src/components/forms/Channel.jsx b/frontend/src/components/forms/Channel.jsx index 53ecddd2..b4c494e1 100644 --- a/frontend/src/components/forms/Channel.jsx +++ b/frontend/src/components/forms/Channel.jsx @@ -11,6 +11,7 @@ import logo from '../../images/logo.png'; import { useChannelLogoSelection } from '../../hooks/useSmartLogos'; import useLogosStore from '../../store/logos'; import LazyLogo from '../LazyLogo'; +import LogoForm from './Logo'; import { Box, Button, @@ -37,7 +38,7 @@ import { import { notifications } from '@mantine/notifications'; import { ListOrdered, SquarePlus, SquareX, X, Zap } from 'lucide-react'; import useEPGsStore from '../../store/epgs'; -import { Dropzone } from '@mantine/dropzone'; + import { FixedSizeList as List } from 'react-window'; import { USER_LEVELS, USER_LEVEL_LABELS } from '../../constants'; @@ -71,7 +72,7 @@ const ChannelForm = ({ channel = null, isOpen, onClose }) => { const tvgs = useEPGsStore((s) => s.tvgs); const tvgsById = useEPGsStore((s) => s.tvgsById); - const [logoPreview, setLogoPreview] = useState(null); + const [logoModalOpen, setLogoModalOpen] = useState(false); const [channelStreams, setChannelStreams] = useState([]); const [channelGroupModelOpen, setChannelGroupModalOpen] = useState(false); const [epgPopoverOpened, setEpgPopoverOpened] = useState(false); @@ -97,33 +98,12 @@ const ChannelForm = ({ channel = null, isOpen, onClose }) => { setChannelStreams(Array.from(streamSet)); }; - const handleLogoChange = async (files) => { - if (files.length === 1) { - const file = files[0]; - - // Validate file size on frontend first - if (file.size > 5 * 1024 * 1024) { - // 5MB - notifications.show({ - title: 'Error', - message: 'File too large. Maximum size is 5MB.', - color: 'red', - }); - return; - } - - try { - const retval = await API.uploadLogo(file); - // Note: API.uploadLogo already adds the logo to the store, no need to fetch - setLogoPreview(retval.cache_url); - formik.setFieldValue('logo_id', retval.id); - } catch (error) { - console.error('Logo upload failed:', error); - // Error notification is already handled in API.uploadLogo - } - } else { - setLogoPreview(null); + const handleLogoSuccess = ({ logo }) => { + if (logo && logo.id) { + formik.setFieldValue('logo_id', logo.id); + ensureLogosLoaded(); // Refresh logos } + setLogoModalOpen(false); }; const handleAutoMatchEpg = async () => { @@ -809,35 +789,13 @@ const ChannelForm = ({ channel = null, isOpen, onClose }) => { - - - - OR - - - - - - Upload Logo - console.log('rejected files', files)} - maxSize={5 * 1024 ** 2} - > - - - Drag images here or click to select files - - - - -
-
+ @@ -1057,6 +1015,12 @@ const ChannelForm = ({ channel = null, isOpen, onClose }) => { isOpen={channelGroupModelOpen} onClose={handleChannelGroupModalClose} /> + + setLogoModalOpen(false)} + onSuccess={handleLogoSuccess} + /> ); }; From d0e31e8acd31d0d2d7938e366a3f664dc4c278ca Mon Sep 17 00:00:00 2001 From: SergeantPanda Date: Fri, 3 Oct 2025 17:11:00 -0500 Subject: [PATCH 2/7] Rebuilt FFmpeg base container From ff894acf4dbbbc227cfb99b0ac9f3d09e1f8fb82 Mon Sep 17 00:00:00 2001 From: Connor Smith Date: Fri, 3 Oct 2025 21:15:45 -0400 Subject: [PATCH 3/7] Fix: Need onSuccess in file upload to update Channel.jsx logo --- frontend/src/components/forms/Logo.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frontend/src/components/forms/Logo.jsx b/frontend/src/components/forms/Logo.jsx index 25847ce8..cb7f385e 100644 --- a/frontend/src/components/forms/Logo.jsx +++ b/frontend/src/components/forms/Logo.jsx @@ -106,13 +106,12 @@ const LogoForm = ({ logo = null, isOpen, onClose, onSuccess }) => { onSuccess?.({ type: 'create', logo: newLogo }); // Call onSuccess for creates } else { // File was uploaded and logo was already created - // Note: API.uploadLogo already calls addLogo() in the store, so no need to call onSuccess notifications.show({ title: 'Success', message: 'Logo uploaded successfully', color: 'green', }); - // No onSuccess call needed - API.uploadLogo already updated the store + onSuccess?.({ type: 'create', logo: uploadResponse }); } onClose(); } catch (error) { From e4a6d19c177af11032cbefb18e3f962e8e44d039 Mon Sep 17 00:00:00 2001 From: SergeantPanda <61642231+SergeantPanda@users.noreply.github.com> Date: Sat, 4 Oct 2025 09:11:38 -0500 Subject: [PATCH 4/7] Revert "Add option to add a logo by a URL in Channel editor/creator" --- frontend/src/components/forms/Channel.jsx | 78 +++++++++++++++++------ frontend/src/components/forms/Logo.jsx | 3 +- 2 files changed, 59 insertions(+), 22 deletions(-) diff --git a/frontend/src/components/forms/Channel.jsx b/frontend/src/components/forms/Channel.jsx index b4c494e1..53ecddd2 100644 --- a/frontend/src/components/forms/Channel.jsx +++ b/frontend/src/components/forms/Channel.jsx @@ -11,7 +11,6 @@ import logo from '../../images/logo.png'; import { useChannelLogoSelection } from '../../hooks/useSmartLogos'; import useLogosStore from '../../store/logos'; import LazyLogo from '../LazyLogo'; -import LogoForm from './Logo'; import { Box, Button, @@ -38,7 +37,7 @@ import { import { notifications } from '@mantine/notifications'; import { ListOrdered, SquarePlus, SquareX, X, Zap } from 'lucide-react'; import useEPGsStore from '../../store/epgs'; - +import { Dropzone } from '@mantine/dropzone'; import { FixedSizeList as List } from 'react-window'; import { USER_LEVELS, USER_LEVEL_LABELS } from '../../constants'; @@ -72,7 +71,7 @@ const ChannelForm = ({ channel = null, isOpen, onClose }) => { const tvgs = useEPGsStore((s) => s.tvgs); const tvgsById = useEPGsStore((s) => s.tvgsById); - const [logoModalOpen, setLogoModalOpen] = useState(false); + const [logoPreview, setLogoPreview] = useState(null); const [channelStreams, setChannelStreams] = useState([]); const [channelGroupModelOpen, setChannelGroupModalOpen] = useState(false); const [epgPopoverOpened, setEpgPopoverOpened] = useState(false); @@ -98,12 +97,33 @@ const ChannelForm = ({ channel = null, isOpen, onClose }) => { setChannelStreams(Array.from(streamSet)); }; - const handleLogoSuccess = ({ logo }) => { - if (logo && logo.id) { - formik.setFieldValue('logo_id', logo.id); - ensureLogosLoaded(); // Refresh logos + const handleLogoChange = async (files) => { + if (files.length === 1) { + const file = files[0]; + + // Validate file size on frontend first + if (file.size > 5 * 1024 * 1024) { + // 5MB + notifications.show({ + title: 'Error', + message: 'File too large. Maximum size is 5MB.', + color: 'red', + }); + return; + } + + try { + const retval = await API.uploadLogo(file); + // Note: API.uploadLogo already adds the logo to the store, no need to fetch + setLogoPreview(retval.cache_url); + formik.setFieldValue('logo_id', retval.id); + } catch (error) { + console.error('Logo upload failed:', error); + // Error notification is already handled in API.uploadLogo + } + } else { + setLogoPreview(null); } - setLogoModalOpen(false); }; const handleAutoMatchEpg = async () => { @@ -789,13 +809,35 @@ const ChannelForm = ({ channel = null, isOpen, onClose }) => { - + + + + OR + + + + + + Upload Logo + console.log('rejected files', files)} + maxSize={5 * 1024 ** 2} + > + + + Drag images here or click to select files + + + + +
+
@@ -1015,12 +1057,6 @@ const ChannelForm = ({ channel = null, isOpen, onClose }) => { isOpen={channelGroupModelOpen} onClose={handleChannelGroupModalClose} /> - - setLogoModalOpen(false)} - onSuccess={handleLogoSuccess} - /> ); }; diff --git a/frontend/src/components/forms/Logo.jsx b/frontend/src/components/forms/Logo.jsx index cb7f385e..25847ce8 100644 --- a/frontend/src/components/forms/Logo.jsx +++ b/frontend/src/components/forms/Logo.jsx @@ -106,12 +106,13 @@ const LogoForm = ({ logo = null, isOpen, onClose, onSuccess }) => { onSuccess?.({ type: 'create', logo: newLogo }); // Call onSuccess for creates } else { // File was uploaded and logo was already created + // Note: API.uploadLogo already calls addLogo() in the store, so no need to call onSuccess notifications.show({ title: 'Success', message: 'Logo uploaded successfully', color: 'green', }); - onSuccess?.({ type: 'create', logo: uploadResponse }); + // No onSuccess call needed - API.uploadLogo already updated the store } onClose(); } catch (error) { From 23be065c525d94829670018a08872ed1cce3cc42 Mon Sep 17 00:00:00 2001 From: SergeantPanda <61642231+SergeantPanda@users.noreply.github.com> Date: Sat, 4 Oct 2025 09:22:09 -0500 Subject: [PATCH 5/7] Revert "Revert "Add option to add a logo by a URL in Channel editor/creator"" --- frontend/src/components/forms/Channel.jsx | 78 ++++++----------------- frontend/src/components/forms/Logo.jsx | 3 +- 2 files changed, 22 insertions(+), 59 deletions(-) diff --git a/frontend/src/components/forms/Channel.jsx b/frontend/src/components/forms/Channel.jsx index 53ecddd2..b4c494e1 100644 --- a/frontend/src/components/forms/Channel.jsx +++ b/frontend/src/components/forms/Channel.jsx @@ -11,6 +11,7 @@ import logo from '../../images/logo.png'; import { useChannelLogoSelection } from '../../hooks/useSmartLogos'; import useLogosStore from '../../store/logos'; import LazyLogo from '../LazyLogo'; +import LogoForm from './Logo'; import { Box, Button, @@ -37,7 +38,7 @@ import { import { notifications } from '@mantine/notifications'; import { ListOrdered, SquarePlus, SquareX, X, Zap } from 'lucide-react'; import useEPGsStore from '../../store/epgs'; -import { Dropzone } from '@mantine/dropzone'; + import { FixedSizeList as List } from 'react-window'; import { USER_LEVELS, USER_LEVEL_LABELS } from '../../constants'; @@ -71,7 +72,7 @@ const ChannelForm = ({ channel = null, isOpen, onClose }) => { const tvgs = useEPGsStore((s) => s.tvgs); const tvgsById = useEPGsStore((s) => s.tvgsById); - const [logoPreview, setLogoPreview] = useState(null); + const [logoModalOpen, setLogoModalOpen] = useState(false); const [channelStreams, setChannelStreams] = useState([]); const [channelGroupModelOpen, setChannelGroupModalOpen] = useState(false); const [epgPopoverOpened, setEpgPopoverOpened] = useState(false); @@ -97,33 +98,12 @@ const ChannelForm = ({ channel = null, isOpen, onClose }) => { setChannelStreams(Array.from(streamSet)); }; - const handleLogoChange = async (files) => { - if (files.length === 1) { - const file = files[0]; - - // Validate file size on frontend first - if (file.size > 5 * 1024 * 1024) { - // 5MB - notifications.show({ - title: 'Error', - message: 'File too large. Maximum size is 5MB.', - color: 'red', - }); - return; - } - - try { - const retval = await API.uploadLogo(file); - // Note: API.uploadLogo already adds the logo to the store, no need to fetch - setLogoPreview(retval.cache_url); - formik.setFieldValue('logo_id', retval.id); - } catch (error) { - console.error('Logo upload failed:', error); - // Error notification is already handled in API.uploadLogo - } - } else { - setLogoPreview(null); + const handleLogoSuccess = ({ logo }) => { + if (logo && logo.id) { + formik.setFieldValue('logo_id', logo.id); + ensureLogosLoaded(); // Refresh logos } + setLogoModalOpen(false); }; const handleAutoMatchEpg = async () => { @@ -809,35 +789,13 @@ const ChannelForm = ({ channel = null, isOpen, onClose }) => { - - - - OR - - - - - - Upload Logo - console.log('rejected files', files)} - maxSize={5 * 1024 ** 2} - > - - - Drag images here or click to select files - - - - -
-
+ @@ -1057,6 +1015,12 @@ const ChannelForm = ({ channel = null, isOpen, onClose }) => { isOpen={channelGroupModelOpen} onClose={handleChannelGroupModalClose} /> + + setLogoModalOpen(false)} + onSuccess={handleLogoSuccess} + /> ); }; diff --git a/frontend/src/components/forms/Logo.jsx b/frontend/src/components/forms/Logo.jsx index 25847ce8..cb7f385e 100644 --- a/frontend/src/components/forms/Logo.jsx +++ b/frontend/src/components/forms/Logo.jsx @@ -106,13 +106,12 @@ const LogoForm = ({ logo = null, isOpen, onClose, onSuccess }) => { onSuccess?.({ type: 'create', logo: newLogo }); // Call onSuccess for creates } else { // File was uploaded and logo was already created - // Note: API.uploadLogo already calls addLogo() in the store, so no need to call onSuccess notifications.show({ title: 'Success', message: 'Logo uploaded successfully', color: 'green', }); - // No onSuccess call needed - API.uploadLogo already updated the store + onSuccess?.({ type: 'create', logo: uploadResponse }); } onClose(); } catch (error) { From 94f966e0275a7f6f70173aa33ca0f6d8b6141362 Mon Sep 17 00:00:00 2001 From: SergeantPanda Date: Sat, 4 Oct 2025 16:25:35 -0500 Subject: [PATCH 6/7] Reverted to old method for parsing xml. Still will not break if Cloudflare adds a new root element. --- apps/epg/tasks.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/epg/tasks.py b/apps/epg/tasks.py index 0f2af709..66ec444e 100644 --- a/apps/epg/tasks.py +++ b/apps/epg/tasks.py @@ -875,17 +875,15 @@ def parse_channels_only(source): if process: logger.debug(f"[parse_channels_only] Memory after opening file: {process.memory_info().rss / 1024 / 1024:.2f} MB") - # Use iterparse to find the element + # Change iterparse to look for both channel and programme elements logger.debug(f"Creating iterparse context for channels and programmes") - tv_finder = etree.iterparse(source_file, events=('start',), tag='tv', remove_blank_text=True, recover=True) - _, tv_root = next(tv_finder) + channel_parser = etree.iterparse(source_file, events=('end',), tag=('channel', 'programme'), remove_blank_text=True, recover=True) if process: logger.debug(f"[parse_channels_only] Memory after creating iterparse: {process.memory_info().rss / 1024 / 1024:.2f} MB") channel_count = 0 total_elements_processed = 0 # Track total elements processed, not just channels - - for elem in tv_root.iter('channel', 'programme'): + for _, elem in channel_parser: total_elements_processed += 1 # Only process channel elements if elem.tag == 'channel': From 29ee837b24b3c4b39bc21d189ff1092b376c8366 Mon Sep 17 00:00:00 2001 From: SergeantPanda Date: Sat, 4 Oct 2025 16:36:49 -0500 Subject: [PATCH 7/7] Add recover=True to iterparse for parse_programs_for_tvg_id as well to fix cloudflare script injection. --- apps/epg/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/epg/tasks.py b/apps/epg/tasks.py index 66ec444e..d9ae5a5d 100644 --- a/apps/epg/tasks.py +++ b/apps/epg/tasks.py @@ -1242,7 +1242,7 @@ def parse_programs_for_tvg_id(epg_id): source_file = open(file_path, 'rb') # Stream parse the file using lxml's iterparse - program_parser = etree.iterparse(source_file, events=('end',), tag='programme', remove_blank_text=True) + program_parser = etree.iterparse(source_file, events=('end',), tag='programme', remove_blank_text=True, recover=True) for _, elem in program_parser: if elem.get('channel') == epg.tvg_id: