Oliver Sluke [Mon, 25 May 2026 11:46:16 +0000 (13:46 +0200)]
dvr: size subscription-title buffer to the title length
`dvr_rec_subscribe` formatted the subscription title into a fixed
100-byte buffer via snprintf. snprintf truncates byte-wise, so a
long multibyte title (CJK at 3 bytes/char, or 4-byte emoji) could
leave a dangling partial UTF-8 sequence at the end. That title
flowed into the comet/ws JSON feed; the invalid byte made the
browser drop the frame, and the web UI stayed silent until the
recording ended.
Size the buffer to the actual title via alloca() so the title
isn't truncated at all. On the common case of short ASCII titles
the buffer is smaller than the previous fixed 100 bytes; on long
multibyte titles it grows just enough. Matches the existing
alloca pattern used elsewhere in the tree (cron.c, http.c,
string_list.c, rtsp.c, htsmsg_xml.c).
Fixes: #2121 Signed-off-by: Oliver Sluke <22557015+oliversluke@users.noreply.github.com>
src/epg.c:2607-2610 — `_epg_sort_channel_descending` delegates to
`_epg_sort_description_ascending` and negates, so a request for
channel-name DESC returns events ordered by reverse description
text instead of reverse channel name. Rows cluster by adjacent
description prose, not by channel.
The sibling `_epg_sort_channel_ascending` (lines 2598-2605) uses
`channel_get_name` correctly, and every other `_descending`
variant in the file delegates to its own `_ascending` (title,
subtitle, summary, description, extratext, genre). Channel is
the only outlier — a copy-paste typo.
Oliver Sluke [Sat, 9 May 2026 06:01:58 +0000 (08:01 +0200)]
mpegts: align DVB delivery-system and modulation defaults with the enum
Several mux classes in mpegts_mux_dvb.c declare a default value
that the corresponding *_enum callback never emits. Each enum
function iterates a small list of DVB_* constants and emits the
canonical string returned by the matching dvb_*2str helper —
which scans the relevant strtab in dvb_support.c and returns
the first matching row.
Two distinct mismatches:
1. Delivery-system defaults
Three classes used the unhyphenated spelling and one carried
the wrong constant entirely (copy-pasted from the dvbt class
above):
delsystab carries both spellings on the same DVB_SYS_*
constant, so configs persisted with the legacy form continue
to load unchanged.
2. QAM constellation / modulation defaults
qamtab carries TWO entries for "auto" — { "AUTO", DVB_MOD_AUTO }
and { "QAM/AUTO", DVB_MOD_QAM_AUTO }. The enum arrays for the
QAM-based modulation classes pass DVB_MOD_QAM_AUTO, so the
dropdown publishes "QAM/AUTO" — but each class declared the
default as "AUTO", which parses to a different constant and
isn't a member of the published enum.
The other DVB tables (modetab, guardtab, hiertab) only carry
a single AUTO entry, and bw / fec fall through to
dvb_common2str which canonicalises value 1 to "AUTO" — so the
remaining 22 MUX_PROP_STR(..., N_("AUTO")) declarations are
already in alignment.
Effect: clients that validate enum membership on submit refuse
to send the default — the user has to manually re-pick the
canonical value before the form will save. The ExtJS combobox
doesn't enforce membership on submit, so the bug went unnoticed
there.
Migration: the delsys side has no on-disk impact (legacy and
canonical strings map to the same constant). For the QAM cases
"AUTO" parses to DVB_MOD_AUTO and "QAM/AUTO" parses to
DVB_MOD_QAM_AUTO — different constants. Existing configs
persisted with the legacy "AUTO" continue to load and tune
correctly (both signal "let the driver decide"); the dropdown
synthesises a current-value option so the form remains editable
until the user re-saves with the canonical choice.
Signed-off-by: Oliver Sluke <22557015+oliversluke@users.noreply.github.com>
Oliver Sluke [Wed, 13 May 2026 21:37:28 +0000 (23:37 +0200)]
intl: include docs translations in the runtime catalog
Makefile line 668 references $(LANGUAGES-DOC) — a variable that
is never defined anywhere in the repository. GNU Make expands
undefined variables to empty, so the docs catalog
(intl/docs/tvheadend.doc.<lang>.po) never reaches PO-FILES, the
poc.py generator, or src/tvh_locale_inc.c. Every msgid sourced
from docs/class/*.md, docs/property/*.md, and docs/wizard/*.md
falls back to English on every language at runtime via
tvh_gettext_lang(), even when the matching translation exists.
User-visible surfaces affected: the setup wizard's per-step
description paragraphs, every per-class help page served via
/markdown/<class>, and every per-property help text.
Repro: build with LANGUAGES=pl, then
grep -c "Welcome to Tvheadend" src/tvh_locale_inc.c
returns 0, even though intl/docs/tvheadend.doc.pl.po carries
the Polish translation
"__Witamy w Tvheadend – Twoim serwerze do transmisji
strumieniowej TV i rejestratorze wideo__"
for the wizard's hello-step headline. After this fix the count
is non-zero and the Polish wizard renders its description in
Polish instead of English.
Coverage today (filled msgstr entries): Polish 100%, French
95.5%, English en_GB / en_US ~100%, Estonian 41.8%, Korean
33.5%, Spanish 20.7%, plus partial work in Norwegian, German,
Danish, Dutch, Swedish, and Bulgarian.
Origin: introduced in commit 7ec64dd70d ("intl: fix the broken
build", 2016-04-13), which added $(wildcard ...) to both
PO-FILES lines but also renamed the docs-line variable from
$(LANGUAGES) to $(LANGUAGES-DOC) without adding a definition.
No prior pull request or GitHub issue has tracked the bug — the
docs catalog has been silently absent from the runtime since.
Two coordinated changes:
1. Makefile — use $(LANGUAGES) on the docs line, matching the
main-catalog line directly above. The existing
`make LANGUAGES="..."` override now affects both catalogs
consistently.
2. support/poc.py — rewrite cstr() to emit every non-ASCII
byte as an octal \NNN escape, producing an ASCII-only
src/tvh_locale_inc.c. The compiled binary contains the
same UTF-8 byte sequence as before (octal escapes resolve
to single bytes at compile time). This avoids tripping
GCC's -Wbidi-chars Trojan-Source check on any
translator-introduced bidi control character — concrete
case: intl/docs/tvheadend.doc.pl.po:6935 translates "The"
as a lone U+202E, almost certainly a stray keystroke.
Octal escapes are bounded to exactly 3 digits by the C
standard, so they don't suffer the greedy-consumption
pitfall of \xHH hex escapes. The rewrite also correctly
escapes literal backslashes, which the previous cstr()
silently passed through.
Catalog ordering: PO-FILES lists the main catalog first and the
docs catalog second; poc.py's load() merges via dict.update(),
so on overlapping msgids the docs translation wins. For Polish
244 msgids overlap (227 identical, 17 different); for French
244 overlap (229 identical, 14 different); for en_GB 244 overlap
(243 identical, 0 different). The differing entries are minor
translator variants — accent / spelling / article-elision tweaks
— not semantic regressions. Examples:
- French "Status": main "Etat" → docs "État"
(accent fix — docs likely more correct)
- French "Channel name": main "Nom de la chaine" → docs
"Nom de chaine" (article dropped, stylistic)
- Polish "Timeshift": main "Funkcja Timeshift" → docs
"Timeshift" (shorter, stylistic)
- Polish "Map unnamed channels":
main "Mapuj nienazwane kanały" → docs
"Mapuj nie nazwane kanały" (spelling variant)
Signed-off-by: Oliver Sluke <22557015+oliversluke@users.noreply.github.com>
Oliver Sluke [Sat, 2 May 2026 20:59:15 +0000 (22:59 +0200)]
htsbuf: escape all JSON control bytes per RFC 8259 §7
`htsbuf_append_and_escape_jsonstr` is the single function every
JSON-emitting code path in tvheadend funnels through (see
`src/htsmsg_json.c:53,67,72,76` — every htsmsg_t serialised to
JSON, which covers every `api/...` REST response and the Comet
bus). Today it escapes only `"`, `\`, `\n`, `\r`, `\t`. The
other 27 ASCII control bytes (0x00–0x1F minus those three) are
appended verbatim.
This violates RFC 8259 §7 / ECMA-404, which both require all
characters in U+0000–U+001F to be escaped. The output is
rejected by every standards-compliant JSON parser (browser
JSON.parse, Python json.loads, jq, Go encoding/json, jansson,
serde_json). The bug surfaces whenever a stored string ever
contained one of those bytes — broadcaster EPG data is a
frequent source (DVB-EIT and XMLTV feeds occasionally carry
stray BEL / SOH / etc.), and any user-editable string field
can pick them up via API clients or paste-from-terminal.
Reproduction (verified on HEAD before this change): set any
DVR entry's `comment` field to a string containing `\a`
via `api/idnode/save`, fetch the entry back via the
corresponding grid endpoint, observe `Invalid control character
at: line 1 column N` from `python3 -c 'import json;
json.load(...)'`. Hex-dump confirms a literal 0x07 byte sits
unescaped inside the JSON string.
Fix: extend the trigger condition to `c < 0x20`, add `\b` and
`\f` shortform escapes (RFC 8259 §7 recognises them; matches
what every standard JSON serialiser emits), fall back to
`\uXXXX` for the remaining ~24 control bytes. Cast to
`unsigned char` so the comparison is well-defined on platforms
where plain `char` is signed. Switch statement replaces the
if-else cascade — same emitted code, easier to read.
Behaviour delta: zero for any string that didn't already
contain a control byte. Only previously-malformed JSON output
becomes well-formed. UTF-8 multi-byte sequences pass through
unchanged (continuation 0x80–0xBF and lead 0xC0–0xFF don't
trigger c < 0x20).
Verified via a small standalone test that links against the
patched htsbuf.o, exercises the function over every byte
0x01–0x1F, and round-trips the output through Python's strict
parser. Output for byte 0x07: `"x\ay"` (escaped). For 0x0C:
`"x\fy"` (named shortform). Etc. All 31 round-trips succeed.
Independent of PR #2034 (cJSON replacement), which explicitly
retains this function.
Signed-off-by: Oliver Sluke <22557015+oliversluke@users.noreply.github.com>
Oliver Sluke [Thu, 23 Apr 2026 21:12:49 +0000 (23:12 +0200)]
channels: fix reentrancy guard in channel_class_changed()
atomic_dec(&ch->ch_changed_ref, 0) subtracts 0 instead of 1, so
the reentrancy counter never resets after the first call. All
subsequent calls see atomic_add() > 0 and return early, permanently
suppressing HTSP channelUpdate notifications, bouquet propagation,
and tag updates for that channel.
Fix by decrementing by 1 so the guard properly resets to 0.
Oliver Sluke [Fri, 24 Apr 2026 13:41:44 +0000 (15:41 +0200)]
htsp: add absolute signal fields for DVBv5 decibel-scale hardware
The signalStatus message only sends feSNR/feSignal for
SIGNAL_STATUS_SCALE_RELATIVE drivers, silently dropping values from
modern DVBv5 hardware that reports in SIGNAL_STATUS_SCALE_DECIBEL.
Add feAbsoluteSNR (s64, dB x 1000) and feAbsoluteSignal (s64,
dBm x 1000) fields for SCALE_DECIBEL drivers, carrying the raw
kernel values with no conversion. Clients check which field is
present to determine the value type.
Pim Zandbergen [Tue, 3 Mar 2026 16:34:24 +0000 (17:34 +0100)]
channels: fix sort order in channel dropdown lists
Channel dropdowns such as "Reuse EPG from" in channel configuration,
and the channel selectors in DVR, autorec, timerec and service mapper,
all use channel_class_get_list() to build their property descriptor.
The descriptor lacked an stype field, causing the ExtJS data store in
idnode.js to re-sort the server-provided list alphabetically by the
displayed string after loading, regardless of the order delivered by
the API.
Fix by adding stype=none to the descriptor to preserve server sort
order, and explicitly requesting the appropriate sort from the API
based on the channel display configuration:
- With channel numbers enabled (chname_num): request sort=numname so
channels are ordered numerically, matching the visible number prefix.
Previously the ExtJS re-sort would produce alphabetical ordering on
the prefixed strings, e.g. "1 ...", "10 ...", "2 ..." instead of
"1 ...", "2 ...", "10 ...".
- With channel numbers disabled: request sort=name to preserve the
existing alphabetical-by-name ordering that users without channel
numbers would expect. This is a pure bug fix with no behaviour
change for this configuration.
Ukn Unknown [Sat, 17 Jan 2026 02:49:30 +0000 (18:49 -0800)]
fix profile when overwritten by user with the wrong value
- fix profile when overwritten by user with the wrong value
- related to the problem : https://github.com/tvheadend/tvheadend/pull/1933#issuecomment-3392478998
- before transferring the profile to ffmpeg we confirm that profile is available.
Ukn Unknown [Sun, 18 Jan 2026 00:44:32 +0000 (16:44 -0800)]
fixes crash when user is forcing pix_fmt or when codec is not available
- fixes https://github.com/tvheadend/tvheadend/issues/1980
- in context.c I added two checks to ensure we don't search for a helper if codec is not available and is not trying to open (tvh_codec_profile_open()) --> this will create a panic.
- in order to avoid getting to that 'late point decision'; at am also checking if pix_fmt is available in ffmpeg before calling.
- if the user will try to force (from web interface) a different pix_fmt some warnings will be printed and video will not open.
Trujulu [Tue, 6 Jan 2026 11:41:48 +0000 (12:41 +0100)]
Allow MUX retune after editing when tune_failed
Currently, when a MUX instance is marked as mmi_tune_failed, it is not being reset. Because of this, after editing the MUX and correcting its parameters, the MUX cannot be used anymore unless the system is restarted. This issue is addressed by setting mmi->mmi_tune_failed to 0 whenever the MUX is saved.
This can be easily reproduced by setting an out-of-range frequency on any Linux DVB tuner and then trying to play with the MUX settings.