Compare commits

...

589 Commits

Author SHA1 Message Date
Patrick
7792b3c276 v2.2.08 QOL (#329)
# Pull Request Template

## Branch
- [x] I branched off beta (not master) to develop this feature/fix

## Description

Fixes issues where the user used a .local address, Samsung TV can't
resolve those, resulting in "Undefined".
Added a additional warning for users who have their TV in Right-to-Left
reading, Samsung TVs revert the order of the IP.

Fixes
 #319 
 #327 

---

## Type of Change

- [x] Bug fix (non-breaking change)
- [x] New feature (non-breaking change)
- [x] Documentation update

---

## Checklist

- [x] My code follows the existing project structure and style  
- [x] I have tested my changes manually  
- [x] I have added necessary documentation (if applicable)  
- [x] I have verified that the installer still works with the underlying
CLI

---

## Additional Notes

Just a QOL improvement
2026-04-23 10:58:39 +02:00
Patrick
abf2dfda8f New Crowdin updates (#328) 2026-04-23 10:39:59 +02:00
Patrick
39fc2179d0 New translations en.json (Portuguese, Brazilian) 2026-04-23 10:31:54 +02:00
Patrick
4321f08f0f New translations en.json (Vietnamese) 2026-04-23 10:31:53 +02:00
Patrick
0f9bd70a3d New translations en.json (English) 2026-04-23 10:31:52 +02:00
Patrick
7ab26caa6a New translations en.json (Chinese Simplified) 2026-04-23 10:31:50 +02:00
Patrick
d65c1371bf New translations en.json (Ukrainian) 2026-04-23 10:31:48 +02:00
Patrick
2fc0739be9 New translations en.json (Turkish) 2026-04-23 10:31:47 +02:00
Patrick
ac4a8f6910 New translations en.json (Swedish) 2026-04-23 10:31:45 +02:00
Patrick
92e505c135 New translations en.json (Serbian (Cyrillic)) 2026-04-23 10:31:44 +02:00
Patrick
1850c487f0 New translations en.json (Russian) 2026-04-23 10:31:42 +02:00
Patrick
89ac524f06 New translations en.json (Portuguese) 2026-04-23 10:31:41 +02:00
Patrick
75cafea579 New translations en.json (Polish) 2026-04-23 10:31:39 +02:00
Patrick
f4d61e36f8 New translations en.json (Norwegian) 2026-04-23 10:31:38 +02:00
Patrick
ddfe2c4983 New translations en.json (Dutch) 2026-04-23 10:31:37 +02:00
Patrick
224c9391aa New translations en.json (Korean) 2026-04-23 10:31:35 +02:00
Patrick
3c6cec56c9 New translations en.json (Japanese) 2026-04-23 10:31:34 +02:00
Patrick
6747fdfc35 New translations en.json (Italian) 2026-04-23 10:31:32 +02:00
Patrick
8e678367f5 New translations en.json (Hungarian) 2026-04-23 10:31:31 +02:00
Patrick
3223d73592 New translations en.json (Hebrew) 2026-04-23 10:31:30 +02:00
Patrick
955418340d New translations en.json (Finnish) 2026-04-23 10:31:28 +02:00
Patrick
8a9ed0eb20 New translations en.json (Greek) 2026-04-23 10:31:27 +02:00
Patrick
9f4c9a7907 New translations en.json (German) 2026-04-23 10:31:25 +02:00
github-actions[bot]
9f90810b94 chore: update version table in README [skip ci] 2026-04-23 08:31:24 +00:00
Patrick
ef23c823df New translations en.json (Danish) 2026-04-23 10:31:24 +02:00
Patrick
cbe0ce8b21 New translations en.json (Czech) 2026-04-23 10:31:23 +02:00
Patrick
c381ecccd1 New translations en.json (Catalan) 2026-04-23 10:31:21 +02:00
Patrick
d1fa426815 New translations en.json (Arabic) 2026-04-23 10:31:20 +02:00
Patrick
377dca8528 New translations en.json (Afrikaans) 2026-04-23 10:31:18 +02:00
Patrick
932f0fa862 New translations en.json (Spanish) 2026-04-23 10:31:17 +02:00
Patrick
fc62f2c034 New translations en.json (French) 2026-04-23 10:31:15 +02:00
Patrick
8c997ce7fd New translations en.json (Romanian) 2026-04-23 10:31:14 +02:00
Patrick Stel
598ded836a version bump 2026-04-23 10:27:29 +02:00
Patrick Stel
9c34f8408e Reversed IP Warning 2026-04-23 10:20:41 +02:00
Patrick
2a2ee67d52 New Crowdin updates (#325) 2026-04-16 09:36:00 +02:00
Patrick
2e5ee8d428 Merge branch 'beta' into l10n_beta 2026-04-16 09:35:00 +02:00
Patrick
1ef3a31f13 New translations en.json (Portuguese, Brazilian) 2026-04-16 05:15:25 +02:00
Patrick
8757689b91 New translations en.json (Vietnamese) 2026-04-16 05:15:24 +02:00
Patrick
37a7c414bd New translations en.json (English) 2026-04-16 05:15:23 +02:00
Patrick
c3659394d5 New translations en.json (Chinese Simplified) 2026-04-16 05:15:21 +02:00
Patrick
3381869811 New translations en.json (Ukrainian) 2026-04-16 05:15:20 +02:00
Patrick
32265caf39 New translations en.json (Turkish) 2026-04-16 05:15:19 +02:00
Patrick
bfc759bfd8 New translations en.json (Swedish) 2026-04-16 05:15:17 +02:00
Patrick
245d47a9e0 New translations en.json (Serbian (Cyrillic)) 2026-04-16 05:15:16 +02:00
Patrick
095d19e8f9 New translations en.json (Russian) 2026-04-16 05:15:15 +02:00
Patrick
527d5ca08a New translations en.json (Portuguese) 2026-04-16 05:15:14 +02:00
Patrick
2da3ee4df2 New translations en.json (Polish) 2026-04-16 05:15:13 +02:00
Patrick
1fe99bd974 New translations en.json (Norwegian) 2026-04-16 05:15:11 +02:00
Patrick
d58ae3ded3 New translations en.json (Dutch) 2026-04-16 05:15:10 +02:00
Patrick
c002f88a7b New translations en.json (Korean) 2026-04-16 05:15:09 +02:00
Patrick
29e746dd09 New translations en.json (Japanese) 2026-04-16 05:15:08 +02:00
Patrick
94307d58db New translations en.json (Italian) 2026-04-16 05:15:06 +02:00
Patrick
b667132efe New translations en.json (Hungarian) 2026-04-16 05:15:05 +02:00
Patrick
8ff66448ad New translations en.json (Hebrew) 2026-04-16 05:15:04 +02:00
Patrick
6085e11046 New translations en.json (Finnish) 2026-04-16 05:15:03 +02:00
Patrick
97e684ea30 New translations en.json (Greek) 2026-04-16 05:15:02 +02:00
Patrick
c19d411677 New translations en.json (German) 2026-04-16 05:15:00 +02:00
Patrick
1786591380 New translations en.json (Danish) 2026-04-16 05:14:59 +02:00
Patrick
2d1eb06048 New translations en.json (Czech) 2026-04-16 05:14:58 +02:00
Patrick
ca18e54097 New translations en.json (Catalan) 2026-04-16 05:14:56 +02:00
Patrick
6ac842b2d0 New translations en.json (Arabic) 2026-04-16 05:14:55 +02:00
Patrick
23d7213bc2 New translations en.json (Afrikaans) 2026-04-16 05:14:54 +02:00
Patrick
fcdea48ba8 New translations en.json (Spanish) 2026-04-16 05:14:53 +02:00
Patrick
9f75dbfefe New translations en.json (French) 2026-04-16 05:14:52 +02:00
Patrick
3ac9aa3bdf New translations en.json (Romanian) 2026-04-16 05:14:50 +02:00
kingchenc
75ac9d6e2e fix: resolve "undefined" server display on Samsung TVs and add mDNS (.local) warning
Fixes #319 — Server appears as "undefined" on Samsung TV after network interruptions
when using mDNS (.local) hostnames.

Root cause:
The injected jellyfin_credentials in localStorage were missing the `Name` property.
When the TV couldn't reach the server (mDNS resolution failure), Jellyfin Web had
no cached server name to display, resulting in literal "undefined".

Changes:
- Add `Name` property to injected auto-login credentials using the real ServerName
  from /System/Info/Public, with fallback to the server URL
- Store JellyfinServerName in AppSettings (fetched during server validation and
  authentication, cleared on logout)
- Add the server's LocalAddress (IP-based) as a fallback entry in config.json so
  the TV can still connect when mDNS resolution fails
- Add a visible warning banner in the UI when a .local hostname is detected,
  advising users to use a direct IP address instead
- Add lblMdnsWarning localization key to all 28 language files (translated for
  da, nl, en, fr, de, pt, tr — English fallback for remaining languages)
2026-04-16 04:51:00 +02:00
github-actions[bot]
a128e1be6b chore: update version table in README [skip ci] 2026-04-11 18:29:12 +00:00
Patrick
f3c81a1400 v2.2.0.7 (#322)
# Pull Request Template

## Branch
- [x] I branched off beta (not master) to develop this feature/fix

## Description

NixOS Shell added by @Confused-Engineer #321 
Merged capability calls to prevent unexpected connection error #318 
CustomWGT Path cleared after installation #320 

---

## Type of Change

- [x] Bug fix (non-breaking change)
- [x] New feature (non-breaking change)
- [x] Documentation update

---

## Checklist

- [x] My code follows the existing project structure and style  
- [x] I have tested my changes manually  
- [x] I have added necessary documentation (if applicable)  
- [x] I have verified that the installer still works with the underlying
CLI
2026-04-11 20:16:44 +02:00
github-actions[bot]
74dbde4276 chore: update version table in README [skip ci] 2026-04-11 18:08:21 +00:00
Patrick
912bc84687 Update AppSettings.cs 2026-04-11 20:04:53 +02:00
Patrick
598274ec08 Add NixOS instructions to README
Added instructions for using NixOS with the Samsung2Jellyfin branch.
2026-04-11 20:01:32 +02:00
Patrick
3db936c767 add shell.nix for NixOS Unstable support (#321)
# Pull Request Template

## Branch
- [x] I branched off beta (not master) to develop this feature/fix

## Description

Please include a summary of the change and which issue is fixed. Also
include relevant motivation and context.

Fixes # (issue number, if applicable)

---

## Type of Change

- [ ] Bug fix (non-breaking change)
- [ ] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [ ] My code follows the existing project structure and style  
- [x] I have tested my changes manually  
- [ ] I have added necessary documentation (if applicable)  
- [x] I have verified that the installer still works with the underlying
CLI

---

## Additional Notes

Any other information that reviewers should know, including limitations,
concerns, or context about the change.
2026-04-11 19:55:44 +02:00
Confused-Engineer
ffc314e6c4 Merge pull request #1 from Confused-Engineer/Confused-Engineer-nix
Upload shell.nix
2026-04-11 10:17:15 -04:00
Confused-Engineer
2d6f5492f8 Upload shell.nix 2026-04-11 10:16:46 -04:00
PatrickSt1991
bafcc8721b Merge branch 'beta' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer into beta 2026-04-11 14:08:03 +02:00
PatrickSt1991
d7424138c6 Clear CustomWgt path after install 2026-04-11 14:06:56 +02:00
Patrick Stel
fa19289e76 Merge branch 'beta' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer into beta 2026-04-10 15:50:49 +02:00
Patrick Stel
3f45b55f02 Single call instead of double preventing connection drop 2026-04-10 15:50:38 +02:00
github-actions[bot]
3916205d56 chore: update version table in README [skip ci] 2026-04-10 12:44:38 +00:00
Patrick
a5e7c0e9ca v2.2.0.6 (#317)
# Pull Request Template

## Branch
- [x] I branched off beta (not master) to develop this feature/fix

## Description

- Added feature by @balcsida, GitHub Token support to prevent rate
limits by using your own GitHub token
- YouTube trailers language fallback by @kingchenc, allowing you to
watch trailers in the selected Jellyfin language
- Network Interface is now saved
- Improved network subnet check

---

## Type of Change

- [x] Bug fix (non-breaking change)
- [x] New feature (non-breaking change)
- [x] Breaking change (fix or feature that would cause existing
functionality to change)
- [x] Documentation update

---

## Checklist

- [x] My code follows the existing project structure and style  
- [x] I have tested my changes manually  
- [x] I have added necessary documentation (if applicable)  
- [x] I have verified that the installer still works with the underlying
CLI
2026-04-10 12:07:43 +02:00
Patrick Stel
d822bd2e9f Feature: Save NetworkInterface
Improvement: Subnet check and IP Search
2026-04-10 11:27:58 +02:00
Patrick
717674ed2f Bump the nuget group with 1 update (#314)
Pinned Tmds.DBus.Protocol at 0.21.3.

[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=Tmds.DBus.Protocol&package-manager=nuget&previous-version=0.21.2&new-version=0.21.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/Jellyfin2Samsung/Samsung-Jellyfin-Installer/network/alerts).

</details>
2026-04-09 21:11:20 +02:00
dependabot[bot]
32b95a72cf Bump the nuget group with 1 update
Bumps Tmds.DBus.Protocol from 0.21.2 to 0.21.3

---
updated-dependencies:
- dependency-name: Tmds.DBus.Protocol
  dependency-version: 0.21.3
  dependency-type: direct:production
  dependency-group: nuget
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-09 16:51:20 +00:00
Patrick
404496e818 Update FileHelper.cs
Removed obsolete file copy for custom WGT and TPK files
2026-04-09 18:49:00 +02:00
github-actions[bot]
11ed846c1a chore: update version table in README [skip ci] 2026-04-05 18:22:07 +00:00
PatrickSt1991
d02526c1d6 Merge branch 'beta' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer into beta 2026-04-05 20:18:57 +02:00
PatrickSt1991
56486c1ec8 Changed IsEnbaled for settings to ServerIpSet 2026-04-05 20:18:48 +02:00
Patrick
a46a352359 New Crowdin updates (#305) 2026-03-30 13:18:45 +02:00
github-actions[bot]
2d0a6525da chore: update version table in README [skip ci] 2026-03-28 06:42:50 +00:00
PatrickSt1991
753fda3407 version bump 2026-03-28 07:39:21 +01:00
Patrick
387c2b501f feat(V17): trailer fallback for non-English metadata languages via TMDB + DuckDuckGo Lite (#304)
Fixes a long-standing Jellyfin upstream bug (jellyfin/jellyfin#12817)
where movies configured with non-English metadata languages (German,
Spanish, French, etc.) show no trailer button because TMDB returns empty
video data for non-English language queries.

## Problem

When Jellyfin fetches metadata with e.g. language=de, TMDB returns 0
trailers even though English trailers exist in their database. Jellyfin
makes no fallback to English. The issue was closed upstream as "not
planned" — unfixed for 2-3 years, affecting every non-English Jellyfin
user.

## Solution

Client-side trailer fallback system built into the existing YouTube fix
(V16 -> V17). No Jellyfin server changes required.

When a movie detail page has no trailer button:

1. TMDB: search for trailer in user's language (via
include_video_language parameter)
2. DuckDuckGo Lite: search "{title} Trailer {language}
site:youtube.com", match by title
3. TMDB: English trailer (cached from step 1, no extra API call)
4. DuckDuckGo Lite: English fallback (cached from step 2, no extra API
call)

Max 2 external HTTP requests (1x TMDB + 1x DDG). Steps 3+4 use cached
results.

## Why DuckDuckGo Lite instead of Google/YouTube search

Google and YouTube aggressively block automated requests with CAPTCHAs,
rate limiting, and bot detection. DuckDuckGo Lite (lite.duckduckgo.com)
returns plain HTML with no JavaScript, no API key required, no CAPTCHAs,
and no bot blocking. DDG Lite has been stable for years with minimal
changes to its HTML structure, making it a reliable fallback for trailer
discovery.

## Technical details

- Language detected from Jellyfin user settings
(document.documentElement.lang), not TV system language
- TMDB API key extracted at runtime from Jellyfin server plugin
configuration (GET /Plugins -> GET /Plugins/{id}/Configuration), falls
back to Jellyfin's default key from TmdbUtils.cs
- DDG results parsed with title extraction: language-matched results
prioritized, non-matched results cached as English fallback
- Trailer button injected as native Jellyfin UI element (same CSS
classes, Material Icons)
- Fullscreen overlay player reuses existing player.html bridge
- Closable via Tizen back button, Escape, Backspace, or video end
- httpsGet helper with 8s timeout and double-callback guard
- 22 non-English languages supported in DDG language map

## Fallback flow

```
Movie detail page loaded
  |
  +-- Trailer button exists? -> Yes -> normal existing fix (CustomPlayer)
  |
  +-- No -> Service: /trailer?tmdbId=X&lang={Jellyfin language}&tmdbKey={runtime key}
              |
              +-- 1. TMDB: trailer in user's language?
              |     -> Found -> play with CustomPlayer
              |
              +-- 2. DDG Lite: "{title} Trailer {language} site:youtube.com"
              |     -> Language matched in title? -> play with CustomPlayer
              |
              +-- 3. TMDB: English trailer (cached from step 1, no extra call)
              |     -> Found -> play with CustomPlayer
              |
              +-- 4. DDG Lite: English fallback (cached from step 2, no extra call)
              |     -> Found -> play with CustomPlayer
              |
              +-- Nothing found -> no trailer available
```

# Pull Request Template

## Branch
- [x] I branched off beta (not master) to develop this feature/fix

## Description

Please include a summary of the change and which issue is fixed. Also
include relevant motivation and context.

Fixes # (issue number, if applicable)

---

## Type of Change

- [x] Bug fix (non-breaking change)
- [x] New feature (non-breaking change)
- [x] Breaking change (fix or feature that would cause existing
functionality to change)
- [-] Documentation update
- [-] Other (please describe):

---

## Checklist

- [x] My code follows the existing project structure and style  
- [-] I have tested my changes manually  
- [-] I have added necessary documentation (if applicable)  
- [-] I have verified that the installer still works with the underlying
CLI
2026-03-28 07:36:55 +01:00
Patrick
016f17ab04 New translations en.json (Portuguese, Brazilian) 2026-03-26 09:43:56 +01:00
Patrick
8e971fc6aa New translations en.json (Vietnamese) 2026-03-26 09:43:55 +01:00
Patrick
ac423c7c04 New translations en.json (English) 2026-03-26 09:43:54 +01:00
Patrick
ec2b9293ee New translations en.json (Chinese Simplified) 2026-03-26 09:43:52 +01:00
Patrick
8cee7d4b97 New translations en.json (Ukrainian) 2026-03-26 09:43:51 +01:00
Patrick
8ecf4a9b8a New translations en.json (Turkish) 2026-03-26 09:43:50 +01:00
Patrick
2e20e6f7b7 New translations en.json (Swedish) 2026-03-26 09:43:49 +01:00
Patrick
eb4ca257f1 New translations en.json (Serbian (Cyrillic)) 2026-03-26 09:43:48 +01:00
Patrick
e6845eebdd New translations en.json (Russian) 2026-03-26 09:43:46 +01:00
Patrick
ebb4995748 New translations en.json (Portuguese) 2026-03-26 09:43:44 +01:00
Patrick
505a2d3be4 New translations en.json (Polish) 2026-03-26 09:43:43 +01:00
Patrick
e31beed93f New translations en.json (Norwegian) 2026-03-26 09:43:42 +01:00
Patrick
dc682cfc5f New translations en.json (Dutch) 2026-03-26 09:43:41 +01:00
Patrick
56f77379c7 New translations en.json (Korean) 2026-03-26 09:43:40 +01:00
Patrick
e09155e9d2 New translations en.json (Japanese) 2026-03-26 09:43:38 +01:00
Patrick
641c2d0f5f New translations en.json (Italian) 2026-03-26 09:43:37 +01:00
Patrick
3a98bc6cf9 New translations en.json (Hungarian) 2026-03-26 09:43:36 +01:00
Patrick
e8491798a5 New translations en.json (Hebrew) 2026-03-26 09:43:35 +01:00
Patrick
bb1bda5051 New translations en.json (Finnish) 2026-03-26 09:43:34 +01:00
Patrick
d6a8d28fbb New translations en.json (Greek) 2026-03-26 09:43:33 +01:00
Patrick
79b1d68643 New translations en.json (German) 2026-03-26 09:43:32 +01:00
Patrick
e852cbc4f1 New translations en.json (Danish) 2026-03-26 09:43:30 +01:00
Patrick
3dcf9a2878 New translations en.json (Czech) 2026-03-26 09:43:29 +01:00
Patrick
6012413efb New translations en.json (Catalan) 2026-03-26 09:43:28 +01:00
Patrick
2aeb46c718 New translations en.json (Arabic) 2026-03-26 09:43:27 +01:00
Patrick
fe0b0b7bcb New translations en.json (Afrikaans) 2026-03-26 09:43:26 +01:00
Patrick
2232312f83 New translations en.json (Spanish) 2026-03-26 09:43:25 +01:00
Patrick
f66bfc69cb New translations en.json (French) 2026-03-26 09:43:24 +01:00
Patrick
0ef964e535 New translations en.json (Romanian) 2026-03-26 09:43:23 +01:00
Patrick
6a3e93d4d6 feat: add GitHub PAT authentication for API requests (#302)
# Pull Request Template

## Branch
- [x] I branched off beta (not master) to develop this feature/fix

## Description

Adds optional GitHub Personal Access Token (PAT) authentication to avoid
API rate limiting when fetching releases. The token is resolved from
three sources in priority order:

1. App settings (`GitHubToken`)
2. `GITHUB_TOKEN` environment variable
3. GitHub CLI (`gh auth token`)

Also improves the rate limit error message with hints on how to
configure a PAT, and adds trace logging so users can see which token
source was used in the debug log.

Related issues (all closed, this PR provides a proper long-term fix via
authenticated requests):
- Relates to #287 — Custom WGT option missing when GitHub rate limit is
reached
- Relates to #235 — Initialization failed due to rate limiting
- Relates to #236 — Catch rate limit exception during initialization
- Relates to #170 — Fix Rate Limit

---

## Type of Change

- [ ] Bug fix (non-breaking change)
- [x] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [x] My code follows the existing project structure and style
- [x] I have tested my changes manually
- [ ] I have added necessary documentation (if applicable)
- [x] I have verified that the installer still works with the underlying
CLI

---

## Additional Notes

- Token is only attached to requests targeting `api.github.com`,
`raw.githubusercontent.com`, and `github.com`
- When no token is configured, the app continues to work with
unauthenticated requests (existing behavior)
- Debug logs now show `[GitHubAuth]` entries indicating which token
source was used
2026-03-26 09:08:04 +01:00
kingchenc
84a4e964a3 feat(V17): extract DDG language map to TrailerLanguageMap.cs with full Jellyfin language coverage
Extracts the inline DDG language map from FixYouTube.cs into a dedicated TrailerLanguageMap.cs file
and expands coverage from 22 languages to all 93 Jellyfin-supported locales.

## Changes

- New file: TrailerLanguageMap.cs — static class with all Jellyfin language codes mapped to
  DDG search keywords (English name + native name per language)
- FixYouTube.cs: inline langMap replaced with __LANG_MAP__ placeholder, injected via .Replace()
  from TrailerLanguageMap.JsObject at build time

## Why

Previously, if a user had a language not in the 22-entry inline map (e.g., Hindi, Bengali, Georgian,
Kazakh, etc.), the DDG search would fall back to an empty string — searching just "{title} Trailer
site:youtube.com" with no language qualifier. This made language matching impossible for those users.

Now every Jellyfin-supported language resolves to proper search keywords. For example:
- hi -> "Hindi हिन्दी"
- ka -> "Georgian ქართული"
- bn -> "Bengali বাংলা"
- kk -> "Kazakh Қазақша"
2026-03-25 22:33:17 +01:00
kingchenc
7bdbc061cc feat(V17): trailer fallback for non-English metadata languages via TMDB + DuckDuckGo Lite
Fixes a long-standing Jellyfin upstream bug (jellyfin/jellyfin#12817) where movies configured with
non-English metadata languages (German, Spanish, French, etc.) show no trailer button because TMDB
returns empty video data for non-English language queries.

## Problem

When Jellyfin fetches metadata with e.g. language=de, TMDB returns 0 trailers even though English
trailers exist in their database. Jellyfin makes no fallback to English. The issue was closed upstream
as "not planned" — unfixed for 2-3 years, affecting every non-English Jellyfin user.

## Solution

Client-side trailer fallback system built into the existing YouTube fix (V16 -> V17). No Jellyfin
server changes required.

When a movie detail page has no trailer button:

1. TMDB: search for trailer in user's language (via include_video_language parameter)
2. DuckDuckGo Lite: search "{title} Trailer {language} site:youtube.com", match by title
3. TMDB: English trailer (cached from step 1, no extra API call)
4. DuckDuckGo Lite: English fallback (cached from step 2, no extra API call)

Max 2 external HTTP requests (1x TMDB + 1x DDG). Steps 3+4 use cached results.

## Why DuckDuckGo Lite instead of Google/YouTube search

Google and YouTube aggressively block automated requests with CAPTCHAs, rate limiting, and bot
detection. DuckDuckGo Lite (lite.duckduckgo.com) returns plain HTML with no JavaScript, no API key
required, no CAPTCHAs, and no bot blocking. DDG Lite has been stable for years with minimal changes
to its HTML structure, making it a reliable fallback for trailer discovery.

## Technical details

- Language detected from Jellyfin user settings (document.documentElement.lang), not TV system language
- TMDB API key extracted at runtime from Jellyfin server plugin configuration (GET /Plugins -> GET
  /Plugins/{id}/Configuration), falls back to Jellyfin's default key from TmdbUtils.cs
- DDG results parsed with title extraction: language-matched results prioritized, non-matched results
  cached as English fallback
- Trailer button injected as native Jellyfin UI element (same CSS classes, Material Icons)
- Fullscreen overlay player reuses existing player.html bridge
- Closable via Tizen back button, Escape, Backspace, or video end
- httpsGet helper with 8s timeout and double-callback guard
- 22 non-English languages supported in DDG language map

## Fallback flow

```
Movie detail page loaded
  |
  +-- Trailer button exists? -> Yes -> normal existing fix (CustomPlayer)
  |
  +-- No -> Service: /trailer?tmdbId=X&lang={Jellyfin language}&tmdbKey={runtime key}
              |
              +-- 1. TMDB: trailer in user's language?
              |     -> Found -> play with CustomPlayer
              |
              +-- 2. DDG Lite: "{title} Trailer {language} site:youtube.com"
              |     -> Language matched in title? -> play with CustomPlayer
              |
              +-- 3. TMDB: English trailer (cached from step 1, no extra call)
              |     -> Found -> play with CustomPlayer
              |
              +-- 4. DDG Lite: English fallback (cached from step 2, no extra call)
              |     -> Found -> play with CustomPlayer
              |
              +-- Nothing found -> no trailer available
```
2026-03-25 21:08:02 +01:00
Dávid Balatoni
f6c6ff074c feat: add GitHub PAT input UI to settings 2026-03-25 19:39:49 +01:00
Dávid Balatoni
ee1a9140cd feat: add trace logging for GitHub token resolution source 2026-03-24 10:36:15 +01:00
Dávid Balatoni
4e27791dfe fix: improve rate limit error message with PAT setup hints 2026-03-21 20:39:09 +01:00
Dávid Balatoni
5518f7f20e feat: wire GitHubAuthHandler into HttpClient singleton 2026-03-21 20:39:05 +01:00
Dávid Balatoni
cea2bda15b feat: add GitHubToken setting to AppSettings 2026-03-21 20:38:46 +01:00
Dávid Balatoni
da91012e06 feat: add GitHub auth handler for authenticated API requests 2026-03-21 20:38:11 +01:00
Patrick
1e33f057bf Update README.md 2026-03-21 14:08:31 +01:00
Patrick
eab08e271d Update Crowdin configuration file 2026-03-21 13:41:36 +01:00
PatrickSt1991
b20803cc5b Merge branch 'beta' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer into beta 2026-03-21 13:35:23 +01:00
PatrickSt1991
b3eb8d007b removed bom from pt.json
Dynamic Localization to work with transifex
2026-03-21 13:23:44 +01:00
Patrick
35990672d2 Add Transifex configuration for localization 2026-03-21 11:50:16 +01:00
Patrick
cc666e4b68 Set checkout reference to 'beta' in workflow 2026-03-21 11:31:46 +01:00
Patrick
70bdf89b4a Sync master-only docs and workflow into beta (#300)
# Pull Request Template

## Branch
- [ ] I branched off beta (not master) to develop this feature/fix

## Description

Please include a summary of the change and which issue is fixed. Also
include relevant motivation and context.

Fixes # (issue number, if applicable)

---

## Type of Change

- [ ] Bug fix (non-breaking change)
- [ ] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [ ] My code follows the existing project structure and style  
- [ ] I have tested my changes manually  
- [ ] I have added necessary documentation (if applicable)  
- [ ] I have verified that the installer still works with the underlying
CLI

---

## Additional Notes

Any other information that reviewers should know, including limitations,
concerns, or context about the change.
2026-03-21 11:26:12 +01:00
Patrick
a6263a2468 Merge branch 'master' into beta 2026-03-21 11:25:59 +01:00
github-actions[bot]
eead46aeb6 chore: update version table in README [skip ci] 2026-03-21 10:13:29 +00:00
PatrickSt1991
b7d1c921d3 Sync master-only docs and workflow into beta 2026-03-21 11:08:23 +01:00
Patrick
8dbb0de4a2 Beta -> Prod (#299)
Added Litefin
2026-03-21 11:02:44 +01:00
github-actions[bot]
868df40a6b chore: update version table in README [skip ci] 2026-03-21 07:01:07 +00:00
Patrick
0b77c32b5e Revise contributor links in README
Updated links and added new contributor information.
2026-03-21 07:59:01 +01:00
Patrick
c0e1f0333f Add LiteFin to the pool (#298)
# Pull Request Template

## Branch
- [x] I branched off beta (not master) to develop this feature/fix

## Description

This includes LiteFin as a new package.

---

## Type of Change

- [ ] Bug fix (non-breaking change)
- [x] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [x] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [x] My code follows the existing project structure and style  
- [x] I have tested my changes manually  
- [x] I have added necessary documentation (if applicable)  
- [x] I have verified that the installer still works with the underlying
CLI

---
2026-03-21 07:57:14 +01:00
PatrickSt1991
78d0ede171 Added litefin and filter for wgt and tpk in selected releases 2026-03-21 07:56:12 +01:00
Patrick
03647d6360 Update AppSettings.cs 2026-03-21 07:15:32 +01:00
Patrick
f17a8404f3 Update AddLatestRelease.cs
wgt and tpk filter
2026-03-21 07:13:30 +01:00
Patrick
3d0f89e6a8 Add fetch call for LiteFinRelease 2026-03-21 07:02:11 +01:00
Patrick
116351376a Add Moonlite to jellyfinOverrides dictionary 2026-03-21 06:57:38 +01:00
Patrick
7dc7ecc6e3 Add Litefin constant for image URL 2026-03-21 06:55:21 +01:00
Patrick
f663eae6d9 Add LiteFinRelease property to AppSettings 2026-03-21 06:49:52 +01:00
Patrick
bfed3237e7 Update Constants.cs 2026-03-15 07:44:56 +01:00
Patrick
fbec97cfad Add Portuguese to available languages in README 2026-03-04 12:54:01 +01:00
Patrick
a48b49e9fc Update pull request template to include branch info
Added a section to specify the branch used for development.
2026-03-04 12:50:33 +01:00
Patrick
3b4e31e09e Change rebase target from master to beta branch 2026-03-04 12:48:59 +01:00
Patrick
9159ee38ac Add Portuguese localization to pt.json (#293)
# Pull Request Template

## Description

Added Portuguese localization for various application messages.

---

## Type of Change

- [ ] Bug fix (non-breaking change)
- [x] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [x] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [x] My code follows the existing project structure and style  
- [ ] I have tested my changes manually  
- [ ] I have added necessary documentation (if applicable)  
- [ ] I have verified that the installer still works with the underlying
CLI

---

## Additional Notes

Any other information that reviewers should know, including limitations,
concerns, or context about the change.
2026-03-04 12:43:25 +01:00
PatrickSt1991
0bdd8cd6ce Add @eduardomozart as contributor 2026-03-04 12:39:37 +01:00
PatrickSt1991
de7edefc9c added Portugees, thanks @eduardomozart 2026-03-04 12:35:06 +01:00
PatrickSt1991
35cb7cf2e6 Add @eduardomozart as contributor 2026-03-04 12:34:49 +01:00
Patrick
ef7b2abb31 Custom WGT Fixes (#291)
# Pull Request Template

## Description

Custom WGT option is now always added even when the GitHub rate limit is
reached
Fixes #287 

Download and Install button is placed inside Try / Catch, should fix the
button being stuck after failed custom wgt
Fixes #285 

---

## Type of Change

- [x] Bug fix (non-breaking change)
- [ ] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [x] My code follows the existing project structure and style  
- [x] I have tested my changes manually  
- [x] I have added necessary documentation (if applicable)  
- [x] I have verified that the installer still works with the underlying
CLI
2026-03-03 09:08:51 +01:00
PatrickSt1991
5ed25031ca Place CustomWGT in try catch #285
Always add Custom WGT option also when rate limit is hit #287
2026-03-03 08:59:50 +01:00
Patrick
37d61eed78 Moonfin URL update (#290)
# Pull Request Template

## Description

This PR is to update the URL for the new Moonfin repo since Tizen and
webOS have been merged

---

## Type of Change

- [x] Bug fix (non-breaking change)
- [ ] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [x] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [x] My code follows the existing project structure and style  
- [x] I have tested my changes manually  
- [x] I have added necessary documentation (if applicable)  
- [x] I have verified that the installer still works with the underlying
CLI

---

## Additional Notes

Any other information that reviewers should know, including limitations,
concerns, or context about the change.
2026-03-03 08:55:46 +01:00
PatrickSt1991
9aa4073995 Add @RadicalMuffinMan as contributor 2026-03-03 08:28:18 +01:00
PatrickSt1991
ecf6937f55 Moonfin URL update 2026-03-03 08:18:52 +01:00
Patrick
23ffe2d046 feat: YouTube Service dynamic service port (#284)
# Pull Request Template

## Description

- Loosens the restrictions on Jellyfin Installations to allow
jellyfin-secondary to be installed next to jellyfin main.
- Sets dynamic port for YouTube Service fix to allow multiple service
for side-by-side running.

Fixes #280 

---

## Type of Change

- [x] Bug fix (non-breaking change)
- [x] New feature (non-breaking change)
- [x] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [x] My code follows the existing project structure and style  
- [x] I have tested my changes manually  
- [x] I have added necessary documentation (if applicable)  
- [x] I have verified that the installer still works with the underlying
CLI
2026-02-22 11:23:43 +01:00
Patrick
1c6ac7a282 Update Linux release status in stable-release.yml
Removed AppImage from the Linux release status message.
2026-02-22 09:35:42 +01:00
Patrick
5f3f8a6895 Update Linux status in beta prerelease workflow 2026-02-22 09:30:51 +01:00
PatrickSt1991
4e9ab2dcb8 Allow secondary Jellyfin to pass even if main is installed
Dynamic port change on youtube trailer service fix for secondary jellyfin
New function to extract appid from config
2026-02-22 09:22:32 +01:00
Patrick
c87b7bef65 Beta (#282)
# Pull Request Template

## Description

Please include a summary of the change and which issue is fixed. Also
include relevant motivation and context.

Fixes # (issue number, if applicable)

---

## Type of Change

- [ ] Bug fix (non-breaking change)
- [ ] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [ ] My code follows the existing project structure and style  
- [ ] I have tested my changes manually  
- [ ] I have added necessary documentation (if applicable)  
- [ ] I have verified that the installer still works with the underlying
CLI

---

## Additional Notes

Any other information that reviewers should know, including limitations,
concerns, or context about the change.
2026-02-21 15:59:59 +01:00
Patrick
60d30c3f05 Add Transport Tycoon Deluxe (#281)
# Pull Request Template

## Description

Please include a summary of the change and which issue is fixed. Also
include relevant motivation and context.

Fixes # (issue number, if applicable)

---

## Type of Change

- [ ] Bug fix (non-breaking change)
- [ ] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [ ] My code follows the existing project structure and style  
- [ ] I have tested my changes manually  
- [ ] I have added necessary documentation (if applicable)  
- [ ] I have verified that the installer still works with the underlying
CLI

---

## Additional Notes

Any other information that reviewers should know, including limitations,
concerns, or context about the change.
2026-02-21 15:43:37 +01:00
Patrick
0cab66098e Add TTD constant for image URL 2026-02-21 15:42:11 +01:00
Patrick
030cf24786 Add TransportTycoonDeluxe preview image 2026-02-21 15:41:03 +01:00
github-actions[bot]
1c12ad6af7 chore: update version table in README [skip ci] 2026-02-21 14:33:06 +00:00
Patrick
3e5d3bfe74 feat: Package required version check (#278)
# Pull Request Template

## Description

Feature added for validating required version against TV Tizen OS
version.
Improved functionality for checking if appId needs to be replaced or
already exists.

---

## Type of Change

- [x] Bug fix (non-breaking change)
- [x] New feature (non-breaking change)
- [x] Breaking change (fix or feature that would cause existing
functionality to change)
- [x] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [x] My code follows the existing project structure and style  
- [x] I have tested my changes manually  
- [x] I have added necessary documentation (if applicable)  
- [x] I have verified that the installer still works with the underlying
CLI
2026-02-21 15:25:27 +01:00
PatrickSt1991
d802bddf52 Merge branch 'beta' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer into beta 2026-02-21 15:18:01 +01:00
skelzer
3f28090e9b Contribution by @skelzer 2026-02-21 15:17:23 +01:00
Patrick
faab2a68df Update TizenCertificateService.cs 2026-02-19 16:40:39 +01:00
github-actions[bot]
6f37097e97 chore: update version table in README [skip ci] 2026-02-18 11:34:32 +00:00
PatrickSt1991
9d4f6b5630 version bump 2026-02-18 12:31:01 +01:00
PatrickSt1991
2c2bb5de80 Package required version check with Tizen OS 2026-02-18 12:02:39 +01:00
Patrick Stel
cd7d17e2b0 NotInstalled Error code added
Catch not installed error code and continue installation flow
2026-02-17 14:31:54 +01:00
Patrick
29381bca2e Beta expected app (#275) 2026-02-16 15:34:12 +01:00
Patrick
4f0232ab6a Fix app ID retrieval in CheckForInstalledApp method 2026-02-16 15:32:22 +01:00
Patrick
28b39c39f5 Implement ReadWgtApplicationId method
Add method to read WGT application ID from config.xml
2026-02-16 15:29:40 +01:00
Patrick
f278f938c0 Refactor CheckForInstalledApp to use expectedAppId (#274)
expectedAppId check instead of using constants

# Pull Request Template

## Description

Please include a summary of the change and which issue is fixed. Also
include relevant motivation and context.

Fixes # (issue number, if applicable)

---

## Type of Change

- [ ] Bug fix (non-breaking change)
- [ ] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [ ] My code follows the existing project structure and style  
- [ ] I have tested my changes manually  
- [ ] I have added necessary documentation (if applicable)  
- [ ] I have verified that the installer still works with the underlying
CLI

---

## Additional Notes

Any other information that reviewers should know, including limitations,
concerns, or context about the change.
2026-02-16 15:20:36 +01:00
Patrick
7d4c94bd62 Refactor CheckForInstalledApp to use expectedAppId
expectedAppId check instead of using constants
2026-02-16 15:19:40 +01:00
PatrickSt1991
e36755de2f Merge branch 'beta' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer into beta 2026-02-14 08:25:54 +01:00
PatrickSt1991
c15405cebb Resolves #265 - Assume installed when app_list is not returned by TV 2026-02-14 08:25:33 +01:00
Patrick
17153786c5 Adds Doom preview image (#272)
Dooms added to the community packages, the PR add the preview image.
2026-02-10 12:57:02 +01:00
Patrick
e5ab14c48e Add doom preview
Adds doom preview image
2026-02-10 12:51:43 +01:00
Patrick
1643ea5758 Update BuildInfoViewModel.cs 2026-02-10 12:50:53 +01:00
Patrick
7906bca1b2 Update Constants.cs 2026-02-10 12:49:48 +01:00
Patrick
9cf3de81ae Refactor stable release workflow and remove AppImage build
Updated the stable release workflow to copy all files from the publish directory and modified the maintainer and description fields in the control file. Removed the AppImage build section.
2026-02-09 12:26:00 +01:00
Patrick
eb10033d19 Beta (#270)
# Pull Request Template

## Description

Please include a summary of the change and which issue is fixed. Also
include relevant motivation and context.

Fixes # (issue number, if applicable)

---

## Type of Change

- [ ] Bug fix (non-breaking change)
- [ ] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [ ] My code follows the existing project structure and style  
- [ ] I have tested my changes manually  
- [ ] I have added necessary documentation (if applicable)  
- [ ] I have verified that the installer still works with the underlying
CLI

---

## Additional Notes

Any other information that reviewers should know, including limitations,
concerns, or context about the change.
2026-02-09 12:24:57 +01:00
PatrickSt1991
91c3aa9549 Revert program.cs logs folder logic 2026-02-09 12:21:03 +01:00
PatrickSt1991
c63adfa618 Merge branch 'beta' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer into beta 2026-02-09 12:11:24 +01:00
PatrickSt1991
4471e27855 Revert "Folder location fix for Linux"
This reverts commit e377ddfd49.
2026-02-09 12:08:40 +01:00
Patrick
7ef1c516e1 Update maintainer and remove AppImage build process
Updated maintainer information and removed AppImage build steps.
2026-02-09 12:03:32 +01:00
github-actions[bot]
2daac548cf chore: update version table in README [skip ci] 2026-02-09 10:13:24 +00:00
PatrickSt1991
e377ddfd49 Folder location fix for Linux 2026-02-09 11:06:10 +01:00
PatrickSt1991
ae4f2ccb2f Merge branch 'beta' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer into beta 2026-02-09 10:26:50 +01:00
PatrickSt1991
64d610d6b2 Different location for Linux 2026-02-09 10:26:42 +01:00
Patrick
88f4755c3d Add dependency installation for AppImage
Install necessary dependencies for AppImage build.
2026-02-09 09:51:17 +01:00
Patrick
d401e750d5 Use recursive copy for publish output in workflows
Updated copy commands to use recursive flag for full directory structure.
2026-02-09 09:47:27 +01:00
Patrick
cd55172fb0 Refactor beta prerelease workflow for AppImage build
Updated the workflow to copy all published files and set up the AppImage structure correctly.
2026-02-09 08:09:37 +01:00
github-actions[bot]
2e495db51d chore: update version table in README [skip ci] 2026-01-30 17:52:26 +00:00
Patrick
cdbc9959c5 Merge branch 'beta' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer into beta (#262)
# Pull Request Template

## Description

Introduce a app preview in the build information 

---

## Type of Change

- [x] Bug fix (non-breaking change)
- [X] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [x] My code follows the existing project structure and style  
- [x] I have tested my changes manually  
- [ ] I have added necessary documentation (if applicable)  
- [x] I have verified that the installer still works with the underlying
CLI

---

## Additional Notes
none
2026-01-30 18:44:24 +01:00
Patrick
60e0148ee2 Update stable-release.yml 2026-01-30 15:56:45 +01:00
github-actions[bot]
b68114e6db chore: update version table in README [skip ci] 2026-01-29 18:37:50 +00:00
PatrickSt1991
51af88217e Merge branch 'beta' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer into beta 2026-01-29 19:34:12 +01:00
PatrickSt1991
c4a4e947ba App preview added 2026-01-29 19:34:02 +01:00
Patrick
fd7058ca57 Add CFBundleShortVersionString and CFBundleIconFile 2026-01-29 11:31:00 +01:00
Patrick
a41a36aad3 Update beta-prerelease.yml 2026-01-29 11:17:43 +01:00
Patrick
1c8bbb7251 Update beta-prerelease.yml 2026-01-29 09:02:04 +01:00
Patrick
7a1729530a Fix duplicate overwrite_files entry
Removed duplicate 'overwrite_files' entry in beta-prerelease.yml.
2026-01-29 08:49:53 +01:00
Patrick
efdb3406fa Refactor macOS icon conversion in workflow
Updated macOS icon conversion process to use librsvg for reliable SVG to PNG conversion.
2026-01-29 08:49:08 +01:00
Patrick
821fac94f8 Enhance stable-release workflow with manual trigger
Added workflow_dispatch event to allow manual triggering with inputs for ref and allow_existing_release.
2026-01-28 13:35:26 +01:00
Patrick
3069290214 Update stable release workflow to allow asset updates 2026-01-28 13:31:47 +01:00
github-actions[bot]
9065cf2249 chore: update version table in README [skip ci] 2026-01-28 12:27:57 +00:00
Patrick
e58a198711 Refactor stable release workflow
Refactor stable release workflow to improve clarity and organization, including version validation, changelog generation, and artifact packaging for Windows, Linux, and macOS.
2026-01-28 13:20:14 +01:00
Patrick
4a3e54bd47 YouTube Trailer fix, multiple bug fixes (#260)
# Pull Request Template

## Description

This version implements a Tizen native fix for YouTube trailers errors,
it creates a web application service that runs behind jellyfin.
Passing the file:// restriction and patching youtube-plugin.js

Along side multiple bug fixes

---

## Type of Change

- [x] Bug fix (non-breaking change)
- [x] New feature (non-breaking change)
- [x] Breaking change (fix or feature that would cause existing
functionality to change)
- [x] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [x] My code follows the existing project structure and style  
- [x] I have tested my changes manually  
- [ ] I have added necessary documentation (if applicable)  
- [x] I have verified that the installer still works with the underlying
CLI

---

## Additional Notes

Any other information that reviewers should know, including limitations,
concerns, or context about the change.
2026-01-28 13:01:02 +01:00
Patrick
9e6eaa2316 Merge branch 'master' into beta 2026-01-28 12:46:41 +01:00
PatrickSt1991
a1cac4ae17 return fix, iframe layover fix
gerneral youtube trailer fix
2026-01-28 12:17:39 +01:00
github-actions[bot]
a48e3b5d24 chore: update version table in README [skip ci] 2026-01-27 20:06:41 +00:00
PatrickSt1991
cc231e39fd version bump 2026-01-27 21:03:00 +01:00
PatrickSt1991
9879412518 fetchReleases instead of release
Added prefix option and int to tell how much releases to fetch
2026-01-27 20:57:47 +01:00
PatrickSt1991
0af0f2d1aa Fixed config.json error when package isnt jellyifin 2026-01-27 20:07:06 +01:00
PatrickSt1991
e91a0f23d6 YouTube fix on Tizen 9 2026-01-27 19:29:36 +01:00
PatrickSt1991
d4fc9b6071 new finding 2026-01-26 21:11:43 +01:00
PatrickSt1991
0979d05234 Wokring resolver 2026-01-25 20:53:52 +01:00
PatrickSt1991
d1078087f5 Merge branch 'beta' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer into beta 2026-01-24 18:27:16 +01:00
PatrickSt1991
63f37a5cfa add esbuild - my bad sorry
beta build service for youtube
2026-01-24 18:23:33 +01:00
PatrickSt1991
867c7c2c6e Added default for Github Assets 2026-01-23 16:12:14 +01:00
Patrick
c572e2b8ed Update beta-prerelease.yml 2026-01-22 19:50:14 +01:00
Patrick
0400671fc4 Update beta-prerelease.yml 2026-01-22 19:48:27 +01:00
Patrick
d58baa4012 Update beta-prerelease.yml 2026-01-22 19:45:36 +01:00
Patrick
2ca2f4c7f0 Update beta-prerelease.yml 2026-01-22 18:15:40 +01:00
Patrick
8d7ab3429a Update beta-prerelease.yml 2026-01-22 12:11:45 +01:00
Patrick
096d7a3e57 Generate Info.plist for Jellyfin2Samsung apps
Added Info.plist generation for both ARM64 and x64 Jellyfin2Samsung apps.
2026-01-22 12:04:04 +01:00
Patrick
276be8cabb Update beta release workflow for version handling
Refactor beta release workflow to remove SKIP_RELEASE logic and improve version extraction.
2026-01-22 11:53:59 +01:00
Patrick
8eef524927 Refactor beta release workflow and version extraction
Removed SKIP_RELEASE logic and related conditions from the beta release workflow. Improved version extraction and release note generation steps.
2026-01-22 11:47:57 +01:00
Patrick
754a47b4dd Refactor AppImage and .app bundle creation steps
Updated AppImage build process and improved .app bundle creation.
2026-01-22 11:30:11 +01:00
Patrick
581f1bb238 Update VERSION_TAG assignment in beta prerelease workflow 2026-01-22 09:36:43 +01:00
github-actions[bot]
669d3896a0 chore: update version table in README [skip ci] 2026-01-22 08:36:24 +00:00
Patrick
a8def26087 Append '-beta' to version in workflow 2026-01-22 09:36:05 +01:00
Patrick
3ea51370cd stip beta tag (#249)
Small PR that strips beta tag so github action can build new release
2026-01-22 09:27:13 +01:00
github-actions[bot]
f280c76a27 chore: update version table in README [skip ci] 2026-01-22 08:26:39 +00:00
Patrick
d59c5e42c5 stip beta tag 2026-01-22 09:23:15 +01:00
kingchenc
04d6f5c228 Fix version 2026-01-21 21:09:00 +01:00
kingchenc
9cd6006841 Implement CI/CD beta workflow, GitHub Atom auto-updater, and critical bug fixes (#247)
So far, everything looks good :)
2026-01-21 21:01:17 +01:00
Patrick
14adc6e806 Merge branch 'master' into beta 2026-01-21 13:41:26 +01:00
PatrickSt1991
e1b9d5b03d possible null references popping up in github action 2026-01-21 13:34:00 +01:00
PatrickSt1991
71cf3d62a8 move functions to htmlutils and filter out beta releases 2026-01-21 13:14:34 +01:00
PatrickSt1991
fed80d8f55 Merge branch 'beta' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer into beta 2026-01-21 12:56:03 +01:00
PatrickSt1991
b36a83038f Write api response to debug log 2026-01-21 12:55:49 +01:00
Patrick
f17efcbefc Add empty line before platform status table in YAML 2026-01-21 11:54:47 +01:00
Patrick
fe16834604 Update beta-prerelease.yml 2026-01-21 11:49:22 +01:00
Patrick
ac5fb567d0 Set bash shell for Create DMG step
Specify bash shell for Create DMG step in workflow.
2026-01-21 11:21:30 +01:00
Patrick
f38c345cc6 Refactor AppImage comment and add macOS packaging
Updated comments and added macOS CLI tarball packaging step.
2026-01-21 11:13:41 +01:00
Patrick
b6ac5b600c Refactor beta-prerelease.yml for clarity and fixes
Updated comments for clarity and fixed AppImage build process to not require FUSE. Adjusted macOS build steps and removed unnecessary comments.
2026-01-21 11:04:23 +01:00
Patrick
36a9a267bc Refactor beta release workflow for clarity and structure 2026-01-21 10:58:41 +01:00
Patrick
7350a0c92d Refactor beta release workflow for improved clarity
Refactor beta release workflow to streamline version extraction, skip logic, and artifact packaging for Linux and macOS. Update commands for building and publishing across platforms.
2026-01-21 10:53:33 +01:00
Patrick
a575a4353b Refactor macOS CLI publishing and update release notes
Updated macOS CLI publishing steps and added universal binary creation. Modified release notes and packaging for macOS and Linux.
2026-01-21 10:46:52 +01:00
PatrickSt1991
2adebebb74 Fixed DeletePreviousInstall setting
Forcefully delete previous installation if tryOverwrite is true but Author certificate mismatch
2026-01-21 08:31:01 +01:00
Patrick
9c8ae5c572 Update beta-prerelease.yml 2026-01-20 22:25:11 +01:00
Patrick
7af3f099cb Update beta-prerelease.yml 2026-01-20 22:19:38 +01:00
Patrick
ad0c3ff186 Rename DMG creation step and improve cleanup
Updated DMG creation step to be safer and cleaner.
2026-01-20 15:19:50 +01:00
Patrick
405573bc36 Update beta-prerelease.yml 2026-01-19 20:49:53 +01:00
Patrick
224fb37350 Update icon download and conversion process in workflow 2026-01-19 20:44:12 +01:00
Patrick
62fc93f10f Update download icon step to use bash shell 2026-01-19 20:37:48 +01:00
Patrick
dea4f4e6c7 Enhance beta release workflow for macOS app
Added macOS app publishing steps and icon conversion.
2026-01-19 20:31:21 +01:00
PatrickSt1991
6a0a6df965 Moved Jellyfin config guard up to installerService
NetworkInterfaces as selectable instead of automatic
2026-01-19 20:05:42 +01:00
kingchenc
9c38699702 Add cancellation token to stop background tasks when update dialog shows
Description

  Fixed UI freeze when update dialog appears during startup. Previously, network scanning and GitHub release loading continued running in the background while the update dialog was displayed, causing the window to freeze.

Changes:
  - Added _initializationCts to cancel initialization tasks
  - Pass cancellation token to LoadReleasesAsync() and LoadDevicesAsync()
  - Cancel background tasks before showing update dialog
  - Resume initialization after user dismisses dialog (Cancel/Skip/Manual)
  - Added cancellation check in DeviceHelper.ScanForDevicesAsync() loop
2026-01-19 13:38:51 +01:00
PatrickSt1991
36b112cd96 Added spacer to prevent youtube falling of the view 2026-01-18 20:01:52 +01:00
kingchenc
0f68d108b1 Add auto-updater with GitHub Atom feed (no rate limit)
Summary
  - Add auto-update checker using GitHub Atom feed (bypasses API rate limits)
  - Show update dialog on startup with two options: "Open Releases" (manual) or "Update Now" (automatic)
  - Automatic updates: download, extract, replace files, restart app
  - Support for Windows, Linux, and macOS

Changes
  - New services: `UpdaterService`, `UpdateDialogService`
  - New dialog: `UpdateDialog` with version info and release notes
  - New settings: `CheckForUpdatesOnStartup`, `SkippedUpdateVersion`
  - Localization: All 6 languages (EN, DE, NL, FR, DA, TR)

Technical Details
  - Uses `/releases.atom` endpoint instead of REST API (no rate limit)
  - Falls back to API only for download URLs when update is available
  - Update script handles file replacement while app is closed
  - Version comparison with semver support
2026-01-18 15:03:23 +01:00
kingchenc
a97d4dccf3 Fix for when no Theme is selected
2026-01-18 14:31:19.230 [Binding]An error occurred binding 'Text' to 'SelectedJellyTheme.Name' at 'SelectedJellyTheme': 'Value is null.' (TextBlock #11601606)
2026-01-18 14:45:36 +01:00
Patrick
2a054c761f Add stable release workflow for Jellyfin2Samsung
This workflow automates the stable release process for the Jellyfin2Samsung project, including validation, changelog generation, and packaging of artifacts.
2026-01-18 14:25:10 +01:00
Patrick
faa67aa904 Update beta-prerelease.yml
Overwrite beta on new commit with same version number
2026-01-18 14:00:20 +01:00
github-actions[bot]
432e50abdf chore: update version table in README [skip ci] 2026-01-18 12:51:56 +00:00
PatrickSt1991
de86a50773 Merge branch 'beta' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer into beta 2026-01-18 13:50:36 +01:00
PatrickSt1991
2e9f1fd350 version bump 2026-01-18 13:49:57 +01:00
PatrickSt1991
a76b2cc3da Replaced DeviceInfo.Duid with tvIpAddress, SDBClient expects TVIP instead DUID 2026-01-18 13:48:44 +01:00
Patrick
b7e2f85251 Refactor beta release workflow to improve clarity 2026-01-18 13:20:30 +01:00
Patrick
95f166124c Refactor beta release workflow and update comments 2026-01-18 13:12:38 +01:00
Patrick
073cd3ebd2 Add GitHub Actions workflow for beta pre-release 2026-01-18 13:04:56 +01:00
Patrick
81ed6dcb81 code cleanup (#238)
Need to go into beta branch
2026-01-18 13:03:37 +01:00
PatrickSt1991
750f571cf4 code clean up and testin github action 2026-01-18 12:53:22 +01:00
Patrick
66d15cc6a8 Add workflow_run trigger for Beta Pre-Release 2026-01-18 12:36:32 +01:00
Patrick
1fab1ae861 Add beta pre-release workflow
This workflow automates the beta pre-release process, including version extraction, changelog generation, publishing for multiple platforms, and creating a GitHub pre-release.
2026-01-18 12:30:05 +01:00
PatrickSt1991
196fe4e89b - GitHub rate limit catched on sdb check
- Developer vs host ip subnet check
- Localization for subnet
2026-01-18 10:03:52 +01:00
kingchenc
9c4c802d6d Fix: Add "IP : Port : Path" server input mode for base path configurations
Users with Jellyfin servers configured with a base path (e.g., http://ip:8096/jellyfin/)
were unable to authenticate. The "IP : Port / Full URL" mode didn't include a base path field, and
while "Full URL" mode worked, it wasn't intuitive for this use case.

The server validation (/System/Info/Public) succeeded, but authentication failed with:
'<' is an invalid start of a value - indicating the API returned HTML instead of JSON
because the base path was missing from the auth endpoint URL.

Fix: Added a third server input mode "IP : Port : Path" that allows users to enter
protocol, IP, port, and base path separately.
2026-01-17 22:11:16 +01:00
kingchenc
0432935f34 Update MainWindow.axaml
Fix dark mode in the main ui in the title
2026-01-17 15:09:54 +01:00
github-actions[bot]
8ee69ef322 chore: update version table in README [skip ci] 2026-01-17 13:42:04 +00:00
PatrickSt1991
dcbb4a0f4c log folder and hidden element 2026-01-17 14:23:09 +01:00
Patrick
37421af16c Ps/refactor (#232)
# Pull Request Template

## Description

Comprehensive redesign featuring a polished, modern UI with dark mode,
smooth transitions. Code refactored for better performance, scalability,
and code maintainability. New features include enhanced CSS options,
YouTube fix.

---

## Type of Change

- [x] Bug fix (non-breaking change)
- [x] New feature (non-breaking change)
- [x] Breaking change (fix or feature that would cause existing
functionality to change)
- [x] Documentation update

---

## Checklist

- [x] My code follows the existing project structure and style  
- [x] I have tested my changes manually  
- [x] I have added necessary documentation (if applicable)  
- [x] I have verified that the installer still works with the underlying
CLI

---

## Additional Notes

This wouldn't have been possible without @kingchenc
2026-01-17 14:12:08 +01:00
PatrickSt1991
1f4032602e v2 release 2026-01-17 14:07:44 +01:00
PatrickSt1991
04992e262e dark mode style fix 2026-01-17 08:11:41 +01:00
PatrickSt1991
00b78f4bbe player fix 2026-01-15 21:08:28 +01:00
PatrickSt1991
d19ad9e600 youtube fix 2026-01-15 21:00:32 +01:00
PatrickSt1991
62ea3023ef fix tv debug button
youtube fix
2026-01-15 20:24:28 +01:00
kingchenc
179a6b5785 UI improvements: clickable theme previews, round dark mode toggle, localization fixes
CSS Style Tab
  - Theme preview images are now clickable - opens the theme's GitHub README page
  - Clear button now also resets the theme preview

Settings
  - Fixed dark mode toggle to be round (matching main window style)
2026-01-12 14:48:31 +01:00
kingchenc
fb2a7e59db Small Localization update 2026-01-12 14:31:54 +01:00
kingchenc
2192fede6a Add Dark Mode toggle with dynamic theme switching
Summary

  - Add dark mode toggle icon (☀️/🌙) to main window and settings header for quick theme switching
  - Create ThemeService for centralized theme management with save/load from settings
  - Replace all hardcoded colors with DynamicResource for proper theme support
  - Synchronize theme state between MainWindow and Settings via ThemeChanged event
  - Update Buttons, TextBlocks, and ComboBoxes styles to be theme-aware
  - Fix dark mode for all modal dialogs (BuildInfo, InstallationComplete, Installing, IpInput, TvLogs)
  - Update DialogService to use theme-aware colors from FluentTheme resources
2026-01-12 14:24:49 +01:00
PatrickSt1991
ab61755609 bitmap fix 2026-01-12 13:58:48 +01:00
PatrickSt1991
f5c18ac044 Merge branch 'ps/refactor' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer into ps/refactor 2026-01-12 13:51:32 +01:00
PatrickSt1991
22f684275c previewer 2026-01-12 13:51:19 +01:00
kingchenc
0ff3130107 Remove UpdateMode dropdown and User Tab (settings now via JS injection)
Summary
  - Remove UpdateMode dropdown from Server tab - settings are now always applied via JS injection
  - Remove User Tab completely - these settings are default in Jellyfin and API calls are unnecessary
  - Simplify JellyfinPackagePatcher to always apply server settings when JellyfinIP is configured
  - Remove UpdateUserConfigurationsAsync API method and InjectUserSettingsAsync HTML injection
  - Clean up related constants, AppSettings properties, and localization strings (6 languages)

Removed
  - ConfigUpdateModes constants and ConfigUpdateMode setting
  - User settings: AutoPlayNextEpisode, RememberAudioSelections, RememberSubtitleSelections, PlayDefaultAudioTrack, UserAutoLogin
  - 8 localization keys across all language files
2026-01-12 13:33:00 +01:00
PatrickSt1991
4237d3a9db Removed RememberIps
Renamed Force Samsung Login label
Renamen Right-to-left label
2026-01-12 13:06:47 +01:00
Patrick
b8fd35b462 Update README.md 2026-01-12 11:14:59 +01:00
Patrick
03808b636b Update README.md 2026-01-12 10:27:41 +01:00
Patrick
9df468fdf2 Update README.md 2026-01-12 09:07:43 +01:00
kingchenc
16fa6f73e9 Add JellyThemes quick-insert buttons to CSS Style tab
Summary
  - Add JellyThemes section to CSS Style tab with 6 pre-configured themes (Obsidian, Solaris, Nebula, Ember, Void, Phantom)
  - Clicking a theme button inserts the CSS @import statement and auto-validates
  - Add "GitHub JellyThemes" link button to open the theme repository
  - Add localization strings for all 6 languages (en, de, nl, da, fr, tr)

New Files
  - Models/JellyTheme.cs - Theme model with metadata (name, icon, color, CSS URL)

Modified Files
  - ViewModels/JellyfinConfigViewModel.cs - JellyThemes collection and commands
  - Views/JellyfinConfigView.axaml - Theme buttons UI with WrapPanel layout
  - All localization JSON files - Added lblJellyThemes and lblJellyThemesHint
2026-01-12 06:20:54 +01:00
kingchenc
3f3709d4a9 Centralize remaining regex patterns and remove dead code
Summary
  - Removed unused SingleOrArrayConverter.cs (dead Newtonsoft.Json code)
  - Centralized all remaining inline regex patterns into RegexPatterns.cs
  - Eliminated last Newtonsoft.Json dependency from codebase

Changes

New Patterns in RegexPatterns.cs
  | Class | Patterns Added |
  |-------|----------------|
  | Html | BaseTag, LocalPaths, CspMeta |
  | KefinTweaks | Loader, TweaksRoot, ScriptEntry |
  | PluginName | NonAlphanumeric |

Files Updated
  - HtmlUtils.cs → uses RegexPatterns.Html.*
  - ProcessHelper.cs → uses RegexPatterns.CommandLine.Arguments
  - JellyfinPluginPatcher.cs → uses RegexPatterns.Html.LinkHref/ScriptSrc
  - KefinTweaksPatch.cs → uses RegexPatterns.KefinTweaks.*
  - PluginPatches.cs → uses RegexPatterns.PluginName.NonAlphanumeric

Result
  - Zero inline Regex.Match/Replace/Matches calls remaining (except dynamic factory patterns)
  - Zero Newtonsoft.Json usage
  - All static patterns pre-compiled for better performance
2026-01-11 22:51:32 +01:00
kingchenc
c50464d44f Merge remote-tracking branch 'origin/master' into ps/refactor 2026-01-11 22:35:32 +01:00
kingchenc
c193ccedd2 Refactor: Consolidate helpers, standardize JSON library, and improve code organization
Summary
  - Introduced centralized helper classes to eliminate code duplication and improve maintainability
  - Standardized JSON serialization from Newtonsoft.Json to System.Text.Json
  - Refactored large methods into focused, testable units
  - Added pre-compiled regex patterns for better performance

New Helper Classes

  | File | Purpose |
  |------|---------|
  | Helpers/Core/Constants.cs | Centralized magic strings, error codes, ports, app identifiers |
  | Helpers/Core/UrlHelper.cs | URL normalization (NormalizeServerUrl, CombineUrl, IsValidHttpUrl) |
  | Helpers/Core/PlatformService.cs | Cross-platform OS detection and platform-specific operations |
  | Helpers/Core/RegexPatterns.cs | Pre-compiled regex patterns for version parsing, Tizen capabilities, HTML, etc. |
  | Helpers/Core/JsonSerializerOptionsProvider.cs | Centralized JsonSerializerOptions configurations |

Key Changes

TizenInstallerService Refactoring
  Split 215-line InstallPackageAsync into focused methods:
  - PrepareDeviceAsync() – Device preparation and diagnostics
  - GetDeviceInfoAsync() – Device information collection
  - HandleCertificateAsync() – Certificate handling logic
  - ApplyConfigurationAsync() – Configuration application
  - HandleInstallationResultAsync() – Result processing

JSON Library Migration
  Migrated from Newtonsoft.Json to System.Text.Json:
  - Models/GitHubRelease.cs
  - Helpers/Core/AddLatestRelease.cs
  - Helpers/API/TizenApiClient.cs
  - Services/SamsungLoginService.cs
  - Services/TizenInstallerService.cs
  - ViewModels/MainWindowViewModel.cs

Code Quality Improvements
  - Replaced 13× duplicate TrimEnd('/') calls with UrlHelper
  - Unified OS detection via PlatformService (previously mixed OperatingSystem.* and RuntimeInformation.*)
  - Used Random.Shared instead of new Random() for thread safety
  - Eliminated magic strings/numbers via Constants class
2026-01-11 22:32:13 +01:00
Patrick
7e63ceab37 Update README.md
add prerequisites step
2026-01-11 18:42:19 +01:00
PatrickSt1991
b9979a9aec Removed old elements from localization files 2026-01-10 21:44:44 +01:00
PatrickSt1991
b348416041 plugin refactor 2026-01-10 21:03:22 +01:00
PatrickSt1991
820e6a19a7 Merge branch 'ps/refactor' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer into ps/refactor 2026-01-10 20:25:42 +01:00
PatrickSt1991
206659e1d6 . 2026-01-10 20:25:38 +01:00
kingchenc
0cc084aef6 Add Admin Multi-User Support & Refresh Users Button
Summary
  - Added admin detection during authentication - checks User.Policy.IsAdministrator from Jellyfin API
  - Admin users can now select multiple Jellyfin users to configure during installation
  - Non-admin users only configure their own account (single-user mode)
  - Added "Refresh" button to reload user list without logout/login
  - Added hint text explaining that selected users will be configured during installation

Changes
  - JellyfinApiClient: Extended AuthenticateAsync() to return isAdmin, added LoadUsersAsync() method
  - AppSettings: Added IsJellyfinAdmin and SelectedUserIds properties
  - JellyfinConfigViewModel: Added admin state, user collections, refresh command
  - JellyfinConfigView.axaml: Added multi-select ListBox for users (admin only), refresh button, hint text
  - TizenInstallerService: Updated to use selected user IDs when admin, single user ID otherwise
  - Localization: Added lblRefreshUsers and lblUserSelectionHint (6 languages)

Behavior
  - Admin login: Shows user list with multi-select, "Refresh" button, configures all selected users
  - Normal user login: No user selection UI, only own account is configured
  - Auth status shows "Authenticated (Admin)" or "Authenticated"
2026-01-10 16:46:01 +01:00
kingchenc
71fdbf8785 Remove Legacy API Key System & Fix Server Input Mode Persistence
Summary
  - Removed obsolete Legacy API Key system - authentication now uses AccessToken from username/password login exclusively
  - Removed user dropdown (LoadUsersAsync, AvailableJellyfinUsers, SelectedJellyfinUser) - no longer needed since you authenticate AS a specific user
  - Removed "everyone" code path from TizenInstallerService
  - Fixed server input mode not persisting after restart (Full URL mode would reset to IP:Port)
  - Added Logout button to allow switching Jellyfin accounts

Changes
  - JellyfinApiClient: Removed `LoadUsersAsync()`, updated auth to use AccessToken
  - JellyfinConfigViewModel: Removed user collections, selection handlers, and LoadJellyfinUsersAsync
  - JellyfinConfigView.axaml: Simplified Auto-Login section, removed user ComboBox
  - AppSettings: Removed `JellyfinApiKey`, added `ServerInputMode` for persistence
  - TizenInstallerService: Simplified to use single authenticated UserId
  - Localization: Cleaned up obsolete labels (6 languages)
2026-01-10 16:23:10 +01:00
kingchenc
9cd31c48c4 Merge Settings UI into Jellyfin Config with new Main Settings tab
Description:
  Consolidates the separate Settings window into the Jellyfin Config window, now renamed to "Settings".

Changes:
  - Added new "Main" tab containing all application settings (Language, Certificate, Local IP, installation options)
  - Merged SettingsViewModel into JellyfinConfigViewModel
  - Updated navigation to open unified Settings window directly from MainWindow
  - Removed obsolete SettingsView and SettingsViewModel files
  - Added localization support for new tab in all 6 languages (EN, DE, DA, FR, NL, TR)

  The Settings window now has 5 tabs: Main, Server, Playback, User, CSS Style
2026-01-10 15:31:55 +01:00
PatrickSt1991
d515779e17 github releases fetch fix 2026-01-09 22:15:09 +01:00
PatrickSt1991
fbe05e662f github releases helper preventing code duplication 2026-01-09 21:51:59 +01:00
PatrickSt1991
302a53f7b1 refactor 2026-01-09 21:11:14 +01:00
PatrickSt1991
5316c4d609 Refactoring helpers 2026-01-09 18:41:21 +01:00
Patrick
83b5143031 Add Custom CSS injection with validation support + Reorganized the Jellyfin Config Server tab layout for better UX flow and moved legacy features to a dedicated section. (#223)
# Pull Request Template

## Description

Please include a summary of the change and which issue is fixed. Also
include relevant motivation and context.

Fixes # (issue number, if applicable)

---

## Type of Change

- [X] Bug fix (non-breaking change)
- [X] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [X] My code follows the existing project structure and style  
- [X] I have tested my changes manually  
- [ ] I have added necessary documentation (if applicable)  
- [ ] I have verified that the installer still works with the underlying
CLI

---

## Additional Notes

Any other information that reviewers should know, including limitations,
concerns, or context about the change.
2026-01-09 14:47:28 +01:00
kingchenc
f3571d6298 Fix installation complete modal closing entire application
Fixed bug where closing the success modal after installation would shut down the entire app instead of just the modal window.

  - Changed CloseWindow() from desktop.Shutdown() to RequestClose event
  - Window now properly subscribes to RequestClose and calls Close()
2026-01-09 01:33:00 +01:00
kingchenc
5f66f952aa Add Custom CSS injection with validation support
Summary:
  - Add new "CSS Style" tab in Jellyfin Config for custom CSS/theme injection
  - Support for inline CSS and @import rules (e.g., ElegantFin, Ultrachromic themes)
  - CSS validation button that tests @import URL reachability and syntax errors
  - Custom CSS is injected as <style> tag into Jellyfin web app during installation

Changes:
  - AppSettings.cs: Add CustomCss property
  - JellyfinConfigViewModel.cs: Add CustomCss observable, validation logic, URL extraction
  - JellyfinConfigView.axaml: New CSS Style tab with multiline TextBox and Validate button
  - JellyfinHtmlPatcher.cs: Add InjectCustomCssAsync() method
  - JellyfinWebPackagePatcher.cs: Call CSS injection during package patching
  - Localization: Add CSS translations for all 6 languages (en, de, fr, nl, tr, da)

Features:
  - Validates @import URLs are reachable before installation
  - Basic CSS syntax validation (balanced braces/parentheses)
  - Button disabled when CSS field is empty
  - Status display with color feedback (green=valid, red=error)
2026-01-09 00:32:12 +01:00
kingchenc
17128965d3 Reorganized the Jellyfin Config Server tab layout for better UX flow and moved legacy features to a dedicated section.
Server Tab now has 3 sections:
  1. Server Settings - Update Mode, Input Mode, Server URL/IP, Connection Status
  2. Auto-Login Settings - User, Password, Authentication
  3. Advanced Settings - TV Debug, Server Scripts, YouTube Fix

User Tab addition:
  - Added "Legacy API Key System" section at bottom
  - Moved API Key field and Jellyfin User(s) dropdown here
  - These are legacy features from WPF era, kept for backward compatibility
2026-01-09 00:32:12 +01:00
github-actions[bot]
2fd644bd53 chore: update version table in README [skip ci] 2026-01-08 19:09:37 +00:00
PatrickSt1991
54db8ba4ca Block config.json changes if the package isnt jellyfin 2026-01-08 19:53:03 +01:00
Patrick
108a4b0d4a Fix ServerMismatch bug, reorganize Jellyfin Config UI with tabs, and improve auto-login flow (#222)
Fix for this PR
5530092a60
and some new stuff :)

### Bug Fixes:

- Fixed ServerMismatch breaking auto-login - The auto-login credentials
were using a fake server ID derived from the URL
(serverUrl.replace(/[^a-zAZ0-9]/g, '')). Now fetches the real server
GUID from /System/Info/Public endpoint and stores it in
AppSettings.JellyfinServerId for correct localStorage credential
injection.
- Fixed URL handling for reverse proxy setups - URLs without explicit
ports (e.g., https://xx.seedhost.eu/xxx/jellyfin/) now work correctly.
Default ports (443 for HTTPS, 80 for HTTP) are no longer included in the
stored URL.
- Auto-enable config patching on login - When authentication succeeds
and Update Mode is "None", it automatically switches to "Server
Settings" to enable config patching. Removed the unused
EnableAutoLoginConfigCommand button.

### UI Improvements:

- Reorganized UI with TabControl - Split settings into 3 tabs:
  - Server: Server settings + Auto-login settings
  - Playback: Browser/playback preferences
  - User: User-specific settings
- Server Input Mode selector - Users can choose between:
  - IP : Port mode - Enter protocol, IP, and port separately
  - Full URL mode - Paste complete URL (auto-parses all fields)
- Removed Base Path field - No longer needed since Full URL mode handles
paths automatically
- Improved button consistency - Test Server and Test Login buttons now
have matching sizes and styling
- Label updates:
  - "Status" → "Server Status"
  - "Test" button → "Test Server"
  - "Login" button → "Test Login"

### Localization:

Added/updated translations for all 6 languages (en, de, fr, nl, tr, da):
 - lblTabServer, lblTabPlayback, lblTabUser
 - lblServerInputMode, lblServerUrl, lblConnectionStatus
 - lblAutoLoginUser, lblTestServer (updated)

### Modified files:

 - JellyfinConfigView.axaml - Complete UI restructure with tabs
- JellyfinConfigViewModel.cs - New properties, auto-validation logic,
server ID fetching
 - JellyfinHtmlPatcher.cs - Fixed auto-login to use real server ID
 - JellyfinPluginPatcher.cs - Exposed API client
 - AppSettings.cs - Added JellyfinServerId, JellyfinServerLocalAddress
 - All localization JSON files - New translation keys

# Pull Request Template

## Description

Please include a summary of the change and which issue is fixed. Also
include relevant motivation and context.

Fixes # (issue number, if applicable)

---

## Type of Change

- [X] Bug fix (non-breaking change)
- [X] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [X] My code follows the existing project structure and style  
- [ ] I have tested my changes manually  
- [ ] I have added necessary documentation (if applicable)  
- [ ] I have verified that the installer still works with the underlying
CLI

---

## Additional Notes

Any other information that reviewers should know, including limitations,
concerns, or context about the change.
2026-01-08 09:48:43 +01:00
kingchenc
063a7e2750 Fix ServerMismatch bug, reorganize Jellyfin Config UI with tabs, and improve auto-login flow
### Bug Fixes:

- Fixed ServerMismatch breaking auto-login - The auto-login credentials were using a fake server ID derived from the URL (serverUrl.replace(/[^a-zAZ0-9]/g, '')). Now fetches the real server GUID from /System/Info/Public endpoint and stores it in AppSettings.JellyfinServerId for correct localStorage credential injection.
- Fixed URL handling for reverse proxy setups - URLs without explicit ports (e.g., https://xx.seedhost.eu/xxx/jellyfin/) now work correctly. Default ports (443 for HTTPS, 80 for HTTP) are no longer included in the stored URL.
- Auto-enable config patching on login - When authentication succeeds and Update Mode is "None", it automatically switches to "Server Settings" to enable
config patching. Removed the unused EnableAutoLoginConfigCommand button.

### UI Improvements:

- Reorganized UI with TabControl - Split settings into 3 tabs:
  - Server: Server settings + Auto-login settings
  - Playback: Browser/playback preferences
  - User: User-specific settings
- Server Input Mode selector - Users can choose between:
  - IP : Port mode - Enter protocol, IP, and port separately
  - Full URL mode - Paste complete URL (auto-parses all fields)
- Removed Base Path field - No longer needed since Full URL mode handles paths automatically
- Improved button consistency - Test Server and Test Login buttons now have matching sizes and styling
- Label updates:
  - "Status" → "Server Status"
  - "Test" button → "Test Server"
  - "Login" button → "Test Login"

### Localization:

Added/updated translations for all 6 languages (en, de, fr, nl, tr, da):
 - lblTabServer, lblTabPlayback, lblTabUser
 - lblServerInputMode, lblServerUrl, lblConnectionStatus
 - lblAutoLoginUser, lblTestServer (updated)

### Modified files:

 - JellyfinConfigView.axaml - Complete UI restructure with tabs
 - JellyfinConfigViewModel.cs - New properties, auto-validation logic, server ID fetching
 - JellyfinHtmlPatcher.cs - Fixed auto-login to use real server ID
 - JellyfinPluginPatcher.cs - Exposed API client
 - AppSettings.cs - Added JellyfinServerId, JellyfinServerLocalAddress
 - All localization JSON files - New translation keys
2026-01-07 22:00:17 +01:00
github-actions[bot]
4fb92c2214 chore: update version table in README [skip ci] 2026-01-07 12:25:21 +00:00
PatrickSt1991
64fc60d543 version bump 2026-01-07 08:15:16 +01:00
Patrick
7640a800d1 Add reverse proxy path support and auto-login credentials injection (#220)
Add support for Jellyfin servers behind reverse proxies (e.g., seedhost)
and automatic login credential injection for Samsung TV installations.

Features:
  - Add Base Path field for reverse proxy URLs (e.g., /xxx/jellyfin)
  - Add JellyfinFullUrl property that combines host:port with base path
  - Add username/password authentication with access token storage
  - Add auto-login injection that pre-populates localStorage credentials
  - Add "Test Connection" button to verify server reachability
  - Add "Login" button to authenticate and store access token

Modified files:
  - Helpers/AppSettings.cs: New properties for base path and credentials
- Helpers/JellyfinApiClient.cs: AuthenticateAsync() and
TestServerConnectionAsync()
- Helpers/JellyfinHtmlPatcher.cs: InjectAutoLoginAsync() for credential
injection
- Helpers/JellyfinWebPackagePatcher.cs: Integrate auto-login during
packaging
  - ViewModels/JellyfinConfigViewModel.cs: UI bindings and commands
  - Views/JellyfinConfigView.axaml: New Auto-Login Settings section
  - Assets/Localization/*.json: Added translations (en, de, fr, nl, da)

Fix for Add reverse proxy path support and auto-login credentials
injection

# Pull Request Template

## Description

Please include a summary of the change and which issue is fixed. Also
include relevant motivation and context.

Fixes # (issue number, if applicable)

---

## Type of Change

- [ ] Bug fix (non-breaking change)
- [X] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [X] My code follows the existing project structure and style  
- [ ] I have tested my changes manually  
- [ ] I have added necessary documentation (if applicable)  
- [ ] I have verified that the installer still works with the underlying
CLI

---

## Additional Notes

Any other information that reviewers should know, including limitations,
concerns, or context about the change.
2026-01-07 08:07:16 +01:00
PatrickSt1991
49dc6cde31 youtube return fix 2026-01-07 08:05:54 +01:00
kingchenc
5530092a60 Add reverse proxy path support and auto-login credentials injection
Add support for Jellyfin servers behind reverse proxies (e.g., seedhost) and automatic login credential injection for Samsung TV installations.

Features:
  - Add Base Path field for reverse proxy URLs (e.g., /xxx/jellyfin)
  - Add JellyfinFullUrl property that combines host:port with base path
  - Add username/password authentication with access token storage
  - Add auto-login injection that pre-populates localStorage credentials
  - Add "Test Connection" button to verify server reachability
  - Add "Login" button to authenticate and store access token

Modified files:
  - Helpers/AppSettings.cs: New properties for base path and credentials
  - Helpers/JellyfinApiClient.cs: AuthenticateAsync() and TestServerConnectionAsync()
  - Helpers/JellyfinHtmlPatcher.cs: InjectAutoLoginAsync() for credential injection
  - Helpers/JellyfinWebPackagePatcher.cs: Integrate auto-login during packaging
  - ViewModels/JellyfinConfigViewModel.cs: UI bindings and commands
  - Views/JellyfinConfigView.axaml: New Auto-Login Settings section
  - Assets/Localization/*.json: Added translations (en, de, fr, nl, da)

Fix for Add reverse proxy path support and auto-login credentials injection
2026-01-07 00:25:33 +01:00
PatrickSt1991
310c7a2fd1 youtube fake 2026-01-06 21:18:13 +01:00
PatrickSt1991
3d962ff0b1 randomize movies editorschoice 2026-01-06 20:58:40 +01:00
PatrickSt1991
50144c0a9e Working trailer! 2026-01-06 19:49:38 +01:00
PatrickSt1991
58909dcd2e Merge branch 'master' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer 2026-01-05 21:47:39 +01:00
PatrickSt1991
86bed3a425 partly working trailer 2026-01-05 21:47:32 +01:00
github-actions[bot]
ccc2bb170d chore: update version table in README [skip ci] 2026-01-05 01:03:57 +00:00
PatrickSt1991
f128790d70 added TR 2026-01-04 21:53:33 +01:00
PatrickSt1991
4d6ce0770a Merge branch 'master' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer 2026-01-04 21:46:12 +01:00
PatrickSt1991
a0813f387f youtube error 2026-01-04 21:46:05 +01:00
Patrick
7bbc7c7674 Add Turkish localization file (#218)
Introduced tr.json to provide Turkish translations for the application's
UI and messages.

# Pull Request Template

## Description

Please include a summary of the change and which issue is fixed. Also
include relevant motivation and context.

Fixes # (issue number, if applicable)

---

## Type of Change

- [ ] Bug fix (non-breaking change)
- [ ] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [x] Other (please describe):

---

## Checklist

- [x] My code follows the existing project structure and style  
- [ ] I have tested my changes manually  
- [ ] I have added necessary documentation (if applicable)  
- [ ] I have verified that the installer still works with the underlying
CLI

---

## Additional Notes

Any other information that reviewers should know, including limitations,
concerns, or context about the change.
2026-01-04 21:24:59 +01:00
Mert Sarac
013a056d75 Add Turkish localization file
Introduced tr.json to provide Turkish translations for the application's UI and messages.
2026-01-04 22:27:07 +03:00
PatrickSt1991
41e2eaaf7d rename functions and added to the matrix 2026-01-04 20:13:33 +01:00
PatrickSt1991
9428364b99 versionbump 2026-01-04 19:35:35 +01:00
PatrickSt1991
a94e2debda Merge branch 'master' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer 2026-01-04 19:34:53 +01:00
PatrickSt1991
2025affc89 fixed editors choice 2026-01-04 19:34:42 +01:00
Patrick
b2020c301a Update badge text for Tizen TV versions 2026-01-04 09:59:28 +01:00
PatrickSt1991
d370bd9c9c HSS Improvement 2026-01-03 21:51:58 +01:00
PatrickSt1991
8aa4f0eb41 localization fix and enabled tag 2026-01-03 09:14:30 +01:00
PatrickSt1991
437c9881ea HSS rewrite
YouTube enablejsapi to 0
2026-01-03 07:31:01 +01:00
PatrickSt1991
8cb0f1d94f Merge branch 'master' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer 2026-01-02 20:56:02 +01:00
PatrickSt1991
bb25f5ebb2 Patch for HSS 2026-01-02 20:55:45 +01:00
github-actions[bot]
40038aa1f2 chore: update version table in README [skip ci] 2026-01-02 18:17:34 +00:00
Patrick
d38e6270cd Small DE fixxes (#216)
https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer/pull/156

It fixes three localizations from my previous PR.
2026-01-02 17:57:39 +01:00
PatrickSt1991
0a1946f692 YouTube Patch preventing Error 153
Jellyfin Plugin improvements
2026-01-02 17:56:28 +01:00
kingchenc
eb541ee586 Small DE fixxes 2026-01-02 17:51:02 +01:00
PatrickSt1991
60c2c8a7cf WS Fix 2026-01-01 22:01:12 +01:00
PatrickSt1991
6eede596c0 Merge branch 'master' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer 2026-01-01 21:06:49 +01:00
PatrickSt1991
ed87ff9be6 Added error codes 118012 and 118, -12
Fixed AppName for InstallationCompleted view
UX Fix for version
2026-01-01 21:06:40 +01:00
github-actions[bot]
79e90e355f chore: update version table in README [skip ci] 2026-01-01 13:01:20 +00:00
PatrickSt1991
534975b559 fix null exception on selectedRelease 2026-01-01 13:55:38 +01:00
PatrickSt1991
c30656a0d7 removed dev stop 2026-01-01 13:47:29 +01:00
PatrickSt1991
a0bcc793a2 beta tag 2026-01-01 13:39:14 +01:00
PatrickSt1991
20079f5d6f version bump 2026-01-01 13:39:01 +01:00
PatrickSt1991
47f09c547a Moved custom WGT from settings to main window under Custom WGT release 2026-01-01 13:38:21 +01:00
PatrickSt1991
3a0af16ad0 translation fix 2025-12-31 19:50:03 +01:00
PatrickSt1991
420f153e40 fixed jellyfin config, readded cors origin to config.xml 2025-12-31 19:36:41 +01:00
PatrickSt1991
04eba3ce7c Merge branch 'master' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer 2025-12-31 16:37:29 +01:00
PatrickSt1991
71b0d413a8 changed ws port to higher number 2025-12-31 16:37:19 +01:00
github-actions[bot]
0f884fdcc8 chore: update version table in README [skip ci] 2025-12-31 14:26:32 +00:00
PatrickSt1991
cdfabac260 New setting to prevent deletion of WGT file, used for debugging wgt files 2025-12-31 14:45:17 +01:00
PatrickSt1991
df55f0d456 Added cancel button as requested in #211 2025-12-31 13:55:09 +01:00
Patrick
f882a2b9a2 Update TizenInstallerService.cs 2025-12-31 08:19:48 +01:00
Patrick
6871e88431 Update AppSettings.cs 2025-12-31 08:06:40 +01:00
Patrick
3232fe2a22 Add notice for older Samsung TV compatibility
Added a notice for older Samsung TVs requiring the Jellyfin Orsay Installer.
2025-12-29 11:53:20 +01:00
github-actions[bot]
0ef39b7e8b chore: update version table in README [skip ci] 2025-12-28 20:36:44 +00:00
PatrickSt1991
2152ba24f9 debug file 2025-12-28 21:13:01 +01:00
PatrickSt1991
6711907c4c Replaced debug.writeline for trace.writeline 2025-12-28 21:08:39 +01:00
PatrickSt1991
764adce7a1 Plugin enhancement
Moonfin double entry fix
2025-12-28 20:52:09 +01:00
PatrickSt1991
a7f8db2183 Merge branch 'master' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer 2025-12-27 20:23:41 +01:00
PatrickSt1991
6efea5d414 116 error is insufficient space 2025-12-27 20:23:33 +01:00
Patrick
961fa02ade Update README.md 2025-12-27 09:04:19 +01:00
github-actions[bot]
5a93c3c08d chore: update version table in README [skip ci] 2025-12-26 07:50:30 +00:00
PatrickSt1991
7c03a513f1 Merge branch 'master' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer 2025-12-26 07:58:01 +01:00
PatrickSt1991
70210550ed additional check to create certificates for non jellyfin 2025-12-26 07:57:44 +01:00
github-actions[bot]
6c69337c01 chore: update version table in README [skip ci] 2025-12-25 21:06:43 +00:00
Patrick
5daaede009 Revise contributor thanks in README.md
Updated acknowledgments for contributors in the README.
2025-12-25 21:29:42 +01:00
PatrickSt1991
cae30ed834 Merge branch 'master' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer 2025-12-25 21:01:46 +01:00
PatrickSt1991
9801b6dfbf Added Moonfin 2025-12-25 21:01:37 +01:00
github-actions[bot]
bf2f54dda7 chore: update version table in README [skip ci] 2025-12-24 07:24:15 +00:00
PatrickSt1991
60cf53c109 removed beta tag 2025-12-24 08:15:36 +01:00
Patrick
4864bd4025 Jellyfin plugins accessible on Samsung TV (#193)
# Pull Request Template

## Description

This PR updates the tooling to detect, validate, and modify Jellyfin
plugins so they can run on Samsung Tizen OS. The tool now transpiles
plugin JavaScript to ES2015, ensuring compatibility with the Tizen
runtime, and loads the processed plugins into the local Tizen
application environment.

With these changes, Jellyfin plugins that previously failed due to
unsupported JavaScript features can now be made compatible and function
correctly on Samsung TVs.

Fixes #177 
---

## Type of Change

- [x] Bug fix (non-breaking change)
- [x] New feature (non-breaking change)
- [x] Breaking change (fix or feature that would cause existing
functionality to change)
- [x] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [x] My code follows the existing project structure and style  
- [x] I have tested my changes manually  
- [x] I have added necessary documentation (if applicable)  
- [x] I have verified that the installer still works with the underlying
CLI

---

## Additional Notes

This change focuses on improving platform compatibility rather than
altering plugin behavior. The transpilation and loading steps are
applied only where required for Tizen OS and do not affect other
environments.

Future improvements could include automated compatibility checks or
clearer error messages when plugins cannot be safely transpiled.
2025-12-24 07:04:13 +01:00
PatrickSt1991
3f5c8604e9 height fix 2025-12-17 16:30:57 +01:00
PatrickSt1991
75b4cb3622 TV Log enhancement 2025-12-17 16:25:08 +01:00
PatrickSt1991
fc0b583b4f version bump 2025-12-16 19:47:01 +01:00
github-actions[bot]
101b6781f0 chore: update version table in README [skip ci] 2025-12-16 18:18:27 +00:00
PatrickSt1991
c1a9ff7ce4 jellyenhanced files and plugin manager fix 2025-12-16 16:12:44 +01:00
Patrick
a934610642 matrix fix 2025-12-16 09:34:19 +01:00
github-actions[bot]
ad4392f576 chore: update version table in README [skip ci] 2025-12-15 18:18:38 +00:00
Patrick
b441c2a5ee added
https://github.com/IAmParadox27/jellyfin-plugin-home-sections
https://github.com/IAmParadox27/jellyfin-plugin-pages
https://github.com/lachlandcp/jellyfin-editors-choice-plugin
2025-12-15 14:02:09 +01:00
Patrick
df8c651d56 Update README.md
Up-to-date readme with new badge
2025-12-13 20:35:09 +01:00
PatrickSt1991
c6c1e481ab theme fix hack 2025-12-13 18:30:56 +01:00
github-actions[bot]
cc91b798df chore: update version table in README [skip ci] 2025-12-13 00:54:02 +00:00
PatrickSt1991
30ffe70141 css 2025-12-12 19:17:42 +01:00
PatrickSt1991
8c25f0366e kefintweaks without css, jellyfin enhanced and media bar 2025-12-12 19:01:36 +01:00
Patrick
617d3438b7 Update index.html 2025-12-12 07:32:53 +01:00
PatrickSt1991
757d9d80a8 kefintweak 2025-12-11 20:01:54 +01:00
PatrickSt1991
b8bf675e30 local esbuild es2015 2025-12-10 20:09:05 +01:00
Patrick
a93fb97aa4 Update README.md 2025-12-10 17:49:28 +01:00
PatrickSt1991
4ee7c26d7e esbuild 2025-12-10 16:20:45 +01:00
PatrickSt1991
ecd42ff8df enhanced fix 2025-12-09 19:52:20 +01:00
PatrickSt1991
25cf8ff4de 3 out of 5 plugins 2025-12-09 16:37:11 +01:00
github-actions[bot]
fdb42a3ff0 chore: update version table in README [skip ci] 2025-12-09 12:23:50 +00:00
PatrickSt1991
65192de7b6 github fallback 2025-12-09 08:56:10 +01:00
PatrickSt1991
4a92effb7c stop decoding code part 2025-12-09 08:13:05 +01:00
PatrickSt1991
49e004cb83 cert fix and plugin scan 2025-12-08 21:49:13 +01:00
PatrickSt1991
9ce2b149c8 skip sanity check on non windows 2025-12-08 19:32:29 +01:00
github-actions[bot]
4eafb70246 chore: update version table in README [skip ci] 2025-12-08 18:18:14 +00:00
PatrickSt1991
442fbaae41 hybrid 2025-12-08 14:43:38 +01:00
PatrickSt1991
fbea16af8f revert replace, natural api error 2025-12-07 16:00:30 +01:00
PatrickSt1991
296d52d75d hybrid 2025-12-07 15:55:22 +01:00
github-actions[bot]
a50dace372 chore: update version table in README [skip ci] 2025-12-06 12:20:11 +00:00
PatrickSt1991
0e55a90054 download local files 2025-12-06 10:05:59 +01:00
github-actions[bot]
1ecc74c156 chore: update version table in README [skip ci] 2025-12-05 00:56:18 +00:00
PatrickSt1991
f5053671f8 version bump 2025-12-04 20:08:53 +01:00
PatrickSt1991
cbf3248f8e required changes to the index.html 2025-12-04 20:07:06 +01:00
PatrickSt1991
2e7fb17433 fix script 2025-12-03 20:26:31 +01:00
PatrickSt1991
5be9747b39 improved injection for websockets 2025-12-03 20:23:27 +01:00
PatrickSt1991
86849454c2 working websocket connection and new features 2025-12-03 20:04:19 +01:00
Patrick
99b228fedc Update index.html 2025-11-30 14:49:06 +01:00
github-actions[bot]
3dd3d0cf67 chore: update version table in README [skip ci] 2025-11-30 13:44:22 +00:00
PatrickSt1991
911a2108a7 csp fix in www/index.html 2025-11-30 14:33:37 +01:00
PatrickSt1991
c71a62e3ca Use server js and css instead of packaged version as requested in #177 enabling plugins.
Styling improvement for settings
2025-11-30 14:11:48 +01:00
Patrick
720d9b36f4 Update README.md 2025-11-29 13:59:03 +01:00
Patrick
b297822475 Update README.md 2025-11-29 07:47:40 +01:00
github-actions[bot]
b8bc90a792 chore: update version table in README [skip ci] 2025-11-28 13:44:43 +00:00
Patrick
6f3686b99d Update README.md 2025-11-28 14:42:22 +01:00
Patrick
08c0512952 Feature/jellyfin url protocol and update app (#178)
# Pull Request Template

## Description

Improvent for Jellyfin server configration by adding combobox for
HTTP/HTTPS
Local IP address is shown for easier debugging
Overwriting existing Jellyfin installation (if author matches)
Community packages are now available
UX Fixes

---

## Type of Change

- [x] Bug fix (non-breaking change)
- [x] New feature (non-breaking change)
- [x] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [x] My code follows the existing project structure and style  
- [x] I have tested my changes manually  
- [ ] I have added necessary documentation (if applicable)  
- [x] I have verified that the installer still works with the underlying
CLI
2025-11-28 14:39:15 +01:00
PatrickSt1991
f4ceb5f71d community packages 2025-11-28 14:32:16 +01:00
Patrick
5f17674620 Update README.md 2025-11-28 10:04:03 +01:00
github-actions[bot]
05074e12a6 chore: update version table in README [skip ci] 2025-11-23 18:14:55 +00:00
PatrickSt1991
689cd693b0 version bump 2025-11-23 15:58:36 +01:00
PatrickSt1991
2cd4034197 new object for tizen 5 2025-11-23 15:56:15 +01:00
PatrickSt1991
9c27ae30d2 Resign logic for older tizen models that don't require a API Certificate 2025-11-23 15:55:40 +01:00
github-actions[bot]
e5bde2680a chore: update version table in README [skip ci] 2025-11-19 18:16:46 +00:00
PatrickSt1991
5adf9fbff6 retry added 2025-11-19 13:19:26 +01:00
PatrickSt1991
3e2f6bec91 version bump 2025-11-19 12:59:13 +01:00
PatrickSt1991
d28331eba3 overwrite option and general improvements 2025-11-19 12:58:40 +01:00
github-actions[bot]
55284e76de chore: update version table in README [skip ci] 2025-11-12 18:16:37 +00:00
PatrickSt1991
6b8f23cba4 beta tag 2025-11-12 14:31:15 +01:00
PatrickSt1991
9ae0073354 improving jellyfin server ip 2025-11-12 14:29:30 +01:00
github-actions[bot]
3c22e7f6bf chore: update version table in README [skip ci] 2025-11-11 18:16:07 +00:00
PatrickSt1991
b3d371aa57 . 2025-11-11 15:16:04 +01:00
PatrickSt1991
13c935d967 non null 2025-11-11 14:31:54 +01:00
PatrickSt1991
a3d236e5e0 Merge branch 'master' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer 2025-11-11 14:21:42 +01:00
PatrickSt1991
bb8f170505 changed app_package_name to app_id to fix uninstall command 2025-11-11 14:21:32 +01:00
github-actions[bot]
78ee3e1460 chore: update version table in README [skip ci] 2025-11-10 18:16:24 +00:00
PatrickSt1991
ad905bdb86 escape permit-install path 2025-11-10 17:17:31 +01:00
PatrickSt1991
0f0347ee29 fixed 2025-11-10 15:50:04 +01:00
Patrick
796268c518 Update TizenInstallerService.cs 2025-11-10 13:06:08 +01:00
Patrick
95596c1153 Fix link to Samsung Jellyfin Installer Wiki FAQ
Updated the link to the Samsung Jellyfin Installer Wiki FAQ.
2025-11-08 14:19:57 +01:00
Patrick
1a1831bc43 Update link to Samsung Jellyfin Installer Wiki FAQ 2025-11-08 14:19:30 +01:00
Patrick
980bb1af5f Fix link to Samsung Jellyfin Installer FAQ
Updated the link in the bug report template to point to the FAQ section.
2025-11-08 14:19:05 +01:00
github-actions[bot]
a8d97e31e7 chore: update version table in README [skip ci] 2025-11-08 12:18:50 +00:00
PatrickSt1991
42d3ea7d5a differentiate between 3.0 and 2.5 tizen os push 2025-11-08 12:57:12 +01:00
Patrick
ef5c2c0b61 Revise README description for clarity and style 2025-11-07 13:46:26 +01:00
github-actions[bot]
bcdee694cd chore: update version table in README [skip ci] 2025-11-07 12:26:30 +00:00
github-actions[bot]
de27559f0e chore: update version table in README [skip ci] 2025-11-07 12:21:35 +00:00
PatrickSt1991
6b9df89bc1 await the conifg update function 2025-11-07 13:18:18 +01:00
Patrick
2fe4f59c93 Add German localization for the application (#156)
# Pull Request Template

## Description

Please include a summary of the change and which issue is fixed. Also
include relevant motivation and context.

Fixes # (issue number, if applicable)

---

## Type of Change

- [ ] Bug fix (non-breaking change)
- [ ] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [X] Other (please describe):

All strings have been translated with appropriate German terminology and
UI conventions.

---

## Checklist

- [X] My code follows the existing project structure and style  
- [ ] I have tested my changes manually  
- [ ] I have added necessary documentation (if applicable)  
- [ ] I have verified that the installer still works with the underlying
CLI

---

## Additional Notes

Any other information that reviewers should know, including limitations,
concerns, or context about the change.
2025-11-07 12:11:14 +01:00
Patrick
8ed0a97f29 Bug/installation 118 tizen 3 (#157)
# Pull Request Template

## Description

AllowPermitInstall is now only triggered for Tizen OS < 4 and instead of
dynamic `sdkToolPath` the `device-profile.xml` is always pushed to
`/home/developer` fixing the 118 error

Fixes Issue #135  and Discussion 149

---

## Type of Change

- [x] Bug fix (non-breaking change)
- [ ] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [x] My code follows the existing project structure and style  
- [x] I have tested my changes manually  
- [ ] I have added necessary documentation (if applicable)  
- [x] I have verified that the installer still works with the underlying
CLI

---

## Additional Notes

None
2025-11-07 11:33:05 +01:00
PatrickSt1991
5e55368393 version bump 2025-11-07 09:26:48 +01:00
PatrickSt1991
ab9d09fca5 typo fix, conpleted to completed 2025-11-07 08:59:48 +01:00
kingchenc
9edc01a0fc Add German localization for the application 2025-11-06 21:51:23 +01:00
github-actions[bot]
c0c924c866 chore: update version table in README [skip ci] 2025-11-06 18:17:06 +00:00
PatrickSt1991
40e17be1e6 version bumpie 2025-11-06 14:18:23 +01:00
PatrickSt1991
20a269ef6e function inside brackets and changed device-profile.xml push location for older devices 2025-11-06 14:17:54 +01:00
Patrick
06efc00bec Assign feature requests to PatrickSt1991 2025-11-05 13:49:50 +01:00
Patrick
40a4a0d6e7 Update generic-bug-report.md 2025-11-05 13:49:34 +01:00
github-actions[bot]
6a762cb9c5 chore: update version table in README [skip ci] 2025-11-05 12:41:27 +00:00
PatrickSt1991
74e2eb0870 message when setting is not allowed 2025-11-05 13:25:53 +01:00
PatrickSt1991
52210ca677 Added auto delete when old version is found after capabilty scan 2025-11-05 13:17:16 +01:00
PatrickSt1991
61d17db09d Merge branch 'master' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer 2025-11-04 13:00:21 +01:00
PatrickSt1991
58c2228693 version bump 2025-11-04 13:00:08 +01:00
github-actions[bot]
2b4ad0799f chore: update version table in README [skip ci] 2025-11-04 11:59:55 +00:00
Patrick
7a63a17b26 Update bug issue template to include log file check
Added a question about log files to the bug issue template.
2025-11-04 12:55:24 +01:00
Patrick
e755794239 Update bug report template to include log file check
Added a question about log files to the bug report template.
2025-11-04 12:55:11 +01:00
Patrick
032552a3af Update bug report template to include log file query
Added a question about including log files in the bug report template.
2025-11-04 12:54:53 +01:00
PatrickSt1991
5acf1467ce better check if jellyfin with appid is already installed and cant be deleted 2025-11-04 12:49:05 +01:00
PatrickSt1991
868215a886 Merge branch 'master' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer 2025-11-04 09:01:08 +01:00
PatrickSt1991
c4713f8201 make the install path dynamic 2025-11-04 09:00:24 +01:00
github-actions[bot]
f5e4c6893d chore: update version table in README [skip ci] 2025-11-04 00:53:33 +00:00
PatrickSt1991
70c34845b2 Merge branch 'master' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer 2025-11-03 21:02:25 +01:00
PatrickSt1991
dfd8569550 Fix for permit install on older tizen devices by extracting the sdk path from the return or use the old path 2025-11-03 21:02:06 +01:00
github-actions[bot]
2094870ef7 chore: update version table in README [skip ci] 2025-11-02 08:38:03 +00:00
PatrickSt1991
c0b365f479 Merge branch 'master' of https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer 2025-11-02 09:31:58 +01:00
PatrickSt1991
4c92f5644a removed permitInstall from settings, making it an automatic process 2025-11-02 09:31:50 +01:00
github-actions[bot]
06c24fcedd chore: update version table in README [skip ci] 2025-11-01 19:15:01 +00:00
Patrick
6ebfd75260 Feature/general improvement (#145)
# Pull Request Template

## Description

Please include a summary of the change and which issue is fixed. Also
include relevant motivation and context.

Fixes # (issue number, if applicable)

---

## Type of Change

- [ ] Bug fix (non-breaking change)
- [ ] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [ ] My code follows the existing project structure and style  
- [ ] I have tested my changes manually  
- [ ] I have added necessary documentation (if applicable)  
- [ ] I have verified that the installer still works with the underlying
CLI

---

## Additional Notes

Any other information that reviewers should know, including limitations,
concerns, or context about the change.
2025-11-01 20:11:09 +01:00
PatrickSt1991
6904770a4f ms seconds time frame
regex improvement
2025-11-01 20:07:45 +01:00
0xEBO
a46290957c chore: credit @0xEBO as contributor 2025-11-01 19:54:22 +01:00
Jeppe Vinkel
36b57fb1aa chore: credit @jeppevinkel as contributor 2025-11-01 19:54:12 +01:00
github-actions[bot]
1c4bbd28ae chore: update version table in README [skip ci] 2025-11-01 14:21:05 +00:00
PatrickSt1991
29b04039ae log argument cut off
fix for device-profile.xml
2025-11-01 15:02:24 +01:00
github-actions[bot]
26805e1de8 chore: update version table in README [skip ci] 2025-10-31 13:07:00 +00:00
PatrickSt1991
0107b9b75e bump 2025-10-31 13:56:48 +01:00
PatrickSt1991
609478bc54 update localization additional check for permit install 2025-10-31 13:56:08 +01:00
github-actions[bot]
9e18da0858 chore: update version table in README [skip ci] 2025-10-31 12:22:09 +00:00
PatrickSt1991
4b65e0e3f8 sanitize log file name added fr to locatiozationservice 2025-10-31 09:24:33 +01:00
Patrick
d45bc8a50b Add French localization to fr.json (#140)
## Description

Add complete French localization for the application including:
- Installation and download messages
- Device connection and network scanning
- Certificate management workflows
- Tizen Studio CLI setup
- Jellyfin configuration options
- User settings and preferences
- Error messages and warnings
- Dialog prompts and confirmations

---

## Type of Change

- [ ] Bug fix (non-breaking change)
- [ ] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [x] Other (please describe): FR localization

---

## Checklist

- [x] My code follows the existing project structure and style  
- [ ] I have tested my changes manually  
- [ ] I have added necessary documentation (if applicable)  
- [ ] I have verified that the installer still works with the underlying
CLI

---

## Additional Notes

Any other information that reviewers should know, including limitations,
concerns, or context about the change.
2025-10-31 09:19:24 +01:00
EBO
6df62eb4f0 Add French localization to fr.json
Added French localization for various application messages.
2025-10-31 09:11:45 +01:00
github-actions[bot]
d0a0de284d chore: update version table in README [skip ci] 2025-10-29 13:34:51 +00:00
PatrickSt1991
230f7d8c36 build addition 2025-10-29 14:25:33 +01:00
PatrickSt1991
9ea3fee454 added debug option 2025-10-29 14:17:26 +01:00
Patrick
c6a1d130d9 Add files via upload 2025-10-29 13:16:02 +01:00
github-actions[bot]
912a9f84d7 chore: update version table in README [skip ci] 2025-10-28 00:51:21 +00:00
PatrickSt1991
c468b0aea1 version 2025-10-27 19:54:22 +01:00
PatrickSt1991
58b407be45 added permit install sdb command
added modify config when app id is compromised
2025-10-25 20:59:00 +02:00
Patrick
4953efdb2e Delete settings.json
Dynamic created file
2025-10-25 12:29:54 +02:00
Patrick
dc7295914b Implement dynamic callback binding and fix asset base path (#132)
- Summary: Replace hardcoded Samsung OAuth callback on port 4794 with
dynamic loopback binding, letting Kestrel choose a free port and
updating redirect URI accordingly. Adjust base path resolution to use
`AppContext.BaseDirectory`, guaranteeing bundled Assets (TizenSDB,
certificates) are located when running from CLI or published output.
- Type of Change: Bug fix.
- Checklist suggestions:
  - Code follows existing structure and style ✔️
- Manual testing via `dotnet run --project
Jellyfin2Samsung-CrossOS/Jellyfin2Samsung.csproj` ✔️
  - Additional documentation not required (mark as N/A)
- Installer CLI verification: note status (e.g., pending if not
executed).
- Additional Notes: Mention Windows permission issue driving dynamic
port work, highlight debug log showing bound port, and state that asset
lookup now works regardless of current working directory.
2025-10-25 12:27:01 +02:00
Ahmed Hamza
7c5868d5af Implement dynamic callback binding and fix asset base path 2025-10-25 09:37:41 +05:30
github-actions[bot]
b6c8c4bcb7 chore: update version table in README [skip ci] 2025-10-24 12:22:01 +00:00
Patrick
3688a11d15 Feature/smart screen (#131)
# Pull Request Template

## Description

- Tool is now able to install Jellyfin on Samsung Smart Devices other
then Smart TVs when Smart TV API isn't available it will fallback to SDB
client to get as much information as possible.

- Tool now contains a version information overview, giving a brief
overview of the available versions and what it is.

---

## Type of Change

- [ ] Bug fix (non-breaking change)
- [x] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Other (please describe):

---

## Checklist

- [x] My code follows the existing project structure and style  
- [x] I have tested my changes manually  
- [x] I have added necessary documentation (if applicable)  
- [x] I have verified that the installer still works with the underlying
CLI

---

## Additional Notes

Versions are being read from Jeppyvinkel readme.md as he creates the wgt
files
2025-10-24 14:13:24 +02:00
PatrickSt1991
363d1fcf47 moved url to app settings 2025-10-24 14:08:13 +02:00
PatrickSt1991
7b5261f870 Version information view and removed beta tag 2025-10-24 14:05:43 +02:00
Patrick
ac4ec0d125 Update generic-bug-report.md 2025-10-24 06:56:41 +02:00
Patrick
b9d1de7e3c Update tizen-6--bug.md 2025-10-24 06:54:42 +02:00
Patrick
69b3e62411 Update tizen-6-5--bug.md 2025-10-24 06:54:12 +02:00
github-actions[bot]
77208e73f8 chore: update version table in README [skip ci] 2025-10-22 18:17:11 +00:00
PatrickSt1991
80e96ad907 added legacy release 2025-10-22 14:58:33 +02:00
Patrick
3e6e9a3890 Update favicon link in index.html 2025-10-21 14:20:56 +02:00
Patrick
e64908536d Update index.html for Jellyfin installation guide 2025-10-21 14:14:19 +02:00
PatrickSt1991
e6330a2a7d removed additional 0 for testing 2025-10-19 15:16:32 +02:00
github-actions[bot]
6332787067 chore: update version table in README [skip ci] 2025-10-19 13:05:58 +00:00
PatrickSt1991
913b7fb06c removed arm from build.ps1 2025-10-19 15:03:52 +02:00
PatrickSt1991
ef8871b730 less restrictive in device searching 2025-10-19 14:49:41 +02:00
Patrick
2009f190c4 Update Linux OS support badge color to brightgreen 2025-10-16 20:44:03 +02:00
Patrick
4a529523e2 Update Linux OS support badge from Beta to Stable 2025-10-16 20:43:17 +02:00
Patrick
ce4118ddf6 Update README.md 2025-10-15 11:29:38 +02:00
github-actions[bot]
f97fc4df06 chore: update version table in README [skip ci] 2025-10-15 09:21:38 +00:00
Patrick
e17f928c3b Feature/non tizen (#123)
# Pull Request Template

## Description

This release introduces significant enhancements to the Tizen App
Installer CLI:

- **Removal of Samsung Tizen Software Dependency:** Eliminates the need
for the official Samsung Tizen software by integrating a [custom-built
SDB client](https://github.com/PatrickSt1991/tizen-sdb), based on
[elfpie’s Tizen App Installer
CLI](https://github.com/elfpie/tizen-app-installer-cli) and
significantly enhanced. Fully supports all required commands and
workflows.
- **OS Hardening:** Improved X509KeyStorageFlags selection for better OS
compatibility.
- **DUID Validation:** Verifies that the DUID in the certificate matches
the DUID reported by the TV.
- **TV Diagnostics:** Determines which actions are permitted on the TV.
- **Package Re-signing:** Packages are re-signed only when necessary.  
- **Package Already Installed:** Displays a notification if a package is
already installed and the TV does not allow uninstallation.
- **Executable Permission for TizenSdb:** Automatically applies `chmod
+x` to the downloaded TizenSdb client.
- **Fixed the Custom WGT Installer:** Users can install a custom WGT
file again.
- **Jellyfin-AVPla Support:** Enables installation of a custom [Jellyfin
build with
AV-Play](https://github.com/PatrickSt1991/tizen-jellyfin-avplay/releases).

Fixes #116 , #118, #119

---

## Type of Change

- [x] Bug fix (non-breaking change)  
- [x] New feature (non-breaking change)  
- [x] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update  
- [ ] Other (please describe):

---

## Checklist

- [x] My code follows the existing project structure and style  
- [x] I have tested my changes manually  
- [x] I have added necessary documentation (if applicable)  
- [x] I have verified that the installer still works with the underlying
CLI

---

## Additional Notes

This release significantly improves usability by removing the dependency
on Samsung’s official software and enhancing security and reliability.
Users can now easily install custom WGT files and Jellyfin AV-Play
builds, while the system verifies permissions and DUIDs to prevent
installation errors.
2025-10-15 11:12:13 +02:00
PatrickSt1991
4151fb38bc fixed custom wgt installation
moved wgtpath from json to runtime
2025-10-15 11:07:14 +02:00
Patrick
2b380810f1 Bump the nuget group with 1 update (#121)
Pinned Microsoft.AspNetCore.Server.Kestrel.Core at 2.3.6.

[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=Microsoft.AspNetCore.Server.Kestrel.Core&package-manager=nuget&previous-version=2.3.0&new-version=2.3.6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer/network/alerts).

</details>
2025-10-14 22:56:51 +02:00
dependabot[bot]
56092dc0b0 Bump the nuget group with 1 update
Bumps Microsoft.AspNetCore.Server.Kestrel.Core from 2.3.0 to 2.3.6

---
updated-dependencies:
- dependency-name: Microsoft.AspNetCore.Server.Kestrel.Core
  dependency-version: 2.3.6
  dependency-type: direct:production
  dependency-group: nuget
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-14 20:42:27 +00:00
Patrick
9b879a4622 Update README.md 2025-10-14 22:39:37 +02:00
github-actions[bot]
3d3a38e7d6 chore: update version table in README [skip ci] 2025-10-14 18:16:18 +00:00
PatrickSt1991
8f83ec0c8e version bump 2025-10-14 19:21:45 +02:00
PatrickSt1991
6509b2dff8 more robust flag check 2025-10-14 19:20:53 +02:00
github-actions[bot]
19f4112e49 chore: update version table in README [skip ci] 2025-10-14 15:58:24 +00:00
PatrickSt1991
bbdbb06a8e increase height on settings view, removed hardcoded os version for testing 2025-10-14 16:27:35 +02:00
PatrickSt1991
77c415ab9f remove hardcoded password 2025-10-14 16:13:56 +02:00
PatrickSt1991
7a4ee1ba4d general bug fixes and improvements 2025-10-14 15:47:06 +02:00
github-actions[bot]
f717057ddf chore: update version table in README [skip ci] 2025-10-13 17:47:26 +00:00
PatrickSt1991
f24c6979e4 KeyStoreFlags for linux and mac
chmod the downloaded executable
2025-10-13 19:37:13 +02:00
github-actions[bot]
e39f8709eb chore: update version table in README [skip ci] 2025-10-12 19:20:37 +00:00
PatrickSt1991
1ba4c9d3cf placeholder and remove some comment 2025-10-12 21:19:58 +02:00
PatrickSt1991
8cd3b58ba2 beta tag 2025-10-12 21:07:53 +02:00
PatrickSt1991
49f353c5a2 removed the need for Tizen Software and moved out of AppData cause no need anymore 2025-10-12 21:07:28 +02:00
PatrickSt1991
653d4f252a added permit install for legacy tizen os 2025-10-10 21:41:11 +02:00
Patrick
810c268f7d Create CNAME 2025-10-09 21:52:31 +02:00
Patrick
b47eb9bc63 Delete CNAME 2025-10-09 21:52:17 +02:00
github-actions[bot]
29d4306045 chore: update version table in README [skip ci] 2025-10-09 19:38:05 +00:00
PatrickSt1991
1f21ce3090 remove macos file
improved build.ps1
2025-10-09 21:27:06 +02:00
PatrickSt1991
63e30ae264 some trace logging and xml fix 2025-10-09 21:26:12 +02:00
PatrickSt1991
27ba396937 version bump 2025-10-09 20:47:52 +02:00
PatrickSt1991
b0887bbb70 profiles.xml fixes 2025-10-09 20:47:27 +02:00
Patrick
12f4bf98cb Update index.html 2025-10-07 11:55:16 +02:00
Patrick
eb54ebb907 Update index.html 2025-10-07 11:29:13 +02:00
Patrick
9415c8daaa Create CNAME 2025-10-07 11:18:09 +02:00
Patrick
ae9c447f2d Update README.md 2025-10-07 11:10:09 +02:00
Patrick
a4d2c6c4b3 Update index.html 2025-10-07 11:09:39 +02:00
Patrick
3f5b46d44b Delete docs/assets directory 2025-10-07 11:08:51 +02:00
github-actions[bot]
0e7595217e chore: update version table in README [skip ci] 2025-10-07 08:11:27 +00:00
Patrick
36ce0fc387 Update update-version-table.yml
Removed legacy, Avalonia has proven its self
2025-10-07 10:06:03 +02:00
Patrick
3b5e1555e4 Update README.md 2025-10-06 22:48:28 +02:00
github-actions[bot]
bd89a4236d chore: update version table in README [skip ci] 2025-10-06 18:15:37 +00:00
PatrickSt1991
a0a909c298 changed setting location 2025-10-06 19:52:16 +02:00
PatrickSt1991
4d99cf78fc different way of building p12 files 2025-10-06 18:40:23 +02:00
github-actions[bot]
199f145e87 chore: update version table in README [skip ci] 2025-10-06 00:51:27 +00:00
github-actions[bot]
d7796b5be0 chore: update version table in README [skip ci] 2025-10-05 18:13:19 +00:00
PatrickSt1991
a2ebac975a Cert fixes 2025-10-05 20:02:20 +02:00
179 changed files with 17107 additions and 3634 deletions

View File

@@ -84,7 +84,7 @@ We welcome pull requests! Please:
```bash
git remote add upstream https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer.git
git fetch upstream
git rebase upstream/master
git rebase upstream/beta
```
---

View File

@@ -3,8 +3,7 @@ name: Feature Request
about: Suggest a new feature or improvement for the application
title: "[Feature] "
labels: enhancement, keep-alive
assignees: ''
assignees: PatrickSt1991
---
**Feature Description**

View File

@@ -3,30 +3,26 @@ name: Generic bug report
about: Create a report to help us improve
title: "[Bug] "
labels: bug, keep-alive
assignees: ''
assignees: PatrickSt1991
---
**Have you checked the wiki?**
[Samsung Jellyfin Installer Wiki](https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer/wiki)
[Samsung Jellyfin Installer Wiki](https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer/wiki/FAQ)
**Have you included the log files in this issue?**
- Yes
- No
**Which version of the application did you use?**
(e.g., v1.2.3)
(e.g., v1.2.3.4)
**Is your Samsung TV in developer mode?**
- Yes
- No
- Not sure
**What Tizen version is your TV running?**
(e.g., Tizen 5.0, 6.5, etc.)
**Was the Tizen CLI installed by the tool at the following location?**
`%LocalAppData%\Programs\TizenStudioCli`
- Yes
- No
- Not sure
**Operating system?**
- Windows
- Linux

View File

@@ -8,25 +8,22 @@ assignees: PatrickSt1991
---
**Have you checked the wiki?**
[Samsung Jellyfin Installer Wiki](https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer/wiki)
[Samsung Jellyfin Installer Wiki](https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer/wiki/FAQ)
**Have you included the log files in this issue?**
- Yes
- No
**Which version of the application did you use?**
(e.g., v1.2.3)
(e.g., v1.2.3.4)
**Is your Samsung TV in developer mode?**
- Yes
- No
- Not sure
**What Tizen version is your TV running?**
(e.g., Tizen 5.0, 6.5, etc.)
**Was the Tizen CLI installed by the tool at the following location?**
`%LocalAppData%\Programs\TizenStudioCli`
- Yes
- No
- Not sure
**Operating system?**
- Windows
- Linux

View File

@@ -8,25 +8,22 @@ assignees: PatrickSt1991
---
**Have you checked the wiki?**
[Samsung Jellyfin Installer Wiki](https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer/wiki)
[Samsung Jellyfin Installer Wiki](https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer/wiki/FAQ)
**Have you included the log files in this issue?**
- Yes
- No
**Which version of the application did you use?**
(e.g., v1.2.3)
(e.g., v1.2.3.4)
**Is your Samsung TV in developer mode?**
- Yes
- No
- Not sure
**What Tizen version is your TV running?**
(e.g., Tizen 5.0, 6.5, etc.)
**Was the Tizen CLI installed by the tool at the following location?**
`%LocalAppData%\Programs\TizenStudioCli`
- Yes
- No
- Not sure
**Operating system?**
- Windows
- Linux

View File

@@ -1,5 +1,8 @@
# Pull Request Template
## Branch
- [ ] I branched off beta (not master) to develop this feature/fix
## Description
Please include a summary of the change and which issue is fixed. Also include relevant motivation and context.

304
.github/workflows/beta-prerelease.yml vendored Normal file
View File

@@ -0,0 +1,304 @@
name: Beta Pre-Release
on:
push:
branches: [beta]
permissions:
contents: write
env:
PRODUCT_NAME: Jellyfin2Samsung
PROJECT_DIR: Jellyfin2Samsung-CrossOS
CONFIGURATION: Release
jobs:
# ======================================================
# RELEASE + WINDOWS + LINUX
# ======================================================
beta-release:
runs-on: ubuntu-latest
outputs:
VERSION: ${{ env.VERSION }}
VERSION_TAG: ${{ env.VERSION_TAG }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
# ---------------- VERSION ----------------
- name: Extract version
shell: bash
run: |
VERSION=$(grep -oE 'v[0-9]+(\.[0-9]+){1,3}' \
Jellyfin2Samsung-CrossOS/Helpers/AppSettings.cs)
if [ -z "$VERSION" ]; then
echo "❌ Version not found"
exit 1
fi
echo "VERSION=${VERSION#v}" >> $GITHUB_ENV
echo "VERSION_TAG=${VERSION}-beta" >> $GITHUB_ENV
# ---------------- RELEASE NOTES ----------------
- name: Generate changelog
run: |
LAST_TAG=$(git tag --sort=-creatordate | grep beta | head -n 1)
RANGE="${LAST_TAG:+$LAST_TAG..HEAD}"
{
echo "## ✅ What's New & Improved"
git log ${RANGE:-HEAD} --pretty=format:"- %s"
} > CHANGELOG.md
- name: Prepare release notes
run: |
DATE=$(date +'%Y-%m-%d')
{
echo "## 📦 [${VERSION_TAG}] ${DATE}"
echo ""
cat CHANGELOG.md
echo ""
echo ""
echo "| Platform | Status | Notes |"
echo "|----------|--------|-------|"
echo "| 🍎 macOS (.app + dmg) | ⚠️ Beta | ARM64 + Intel |"
echo "| 🍎 macOS (CLI) | ⚠️ Beta | Per-arch tar.gz |"
echo "| 🐧 Linux | ⚠️ Beta | tar.gz / .deb |"
echo "| 🪟 Windows | ⚠️ Beta | CI-built |"
} > RELEASE_NOTES.md
- uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
# ---------------- WINDOWS ----------------
- name: Build Windows
run: |
dotnet publish Jellyfin2Samsung-CrossOS/Jellyfin2Samsung.csproj \
-c Release -r win-x64 -p:SelfContained=true \
-o publish/win-x64
mkdir -p dist
(cd publish/win-x64 && zip -r "../../dist/${PRODUCT_NAME}-${VERSION_TAG}-win-x64.zip" .)
# ---------------- LINUX ----------------
- name: Build Linux
run: |
dotnet publish Jellyfin2Samsung-CrossOS/Jellyfin2Samsung.csproj \
-c Release -r linux-x64 -p:SelfContained=true \
-o publish/linux-x64
mkdir -p dist
tar -czf "dist/${PRODUCT_NAME}-${VERSION_TAG}-linux-x64.tar.gz" \
-C publish/linux-x64 .
# ---------------- LINUX ICON ----------------
- name: Linux icon
run: |
sudo apt-get update
sudo apt-get install -y librsvg2-bin
rsvg-convert -w 256 -h 256 \
jellyfin-tizen-logo.svg -o jellyfin.png
# ---------------- DEB ----------------
- name: Build .deb
run: |
sudo apt-get install -y dpkg-dev
PKG=deb
mkdir -p $PKG/opt/jellyfin2samsung \
$PKG/usr/share/{applications,icons/hicolor/256x256/apps}
cp -R publish/linux-x64/. $PKG/opt/jellyfin2samsung/
chmod +x $PKG/opt/jellyfin2samsung/Jellyfin2Samsung
# 🔥 quick+dirty: allow writes where your app writes
mkdir -p \
$PKG/opt/jellyfin2samsung/Logs \
$PKG/opt/jellyfin2samsung/Downloads \
$PKG/opt/jellyfin2samsung/Assets/TizenSDB \
$PKG/opt/jellyfin2samsung/Assets/Certificate
chmod -R 777 \
$PKG/opt/jellyfin2samsung/Logs \
$PKG/opt/jellyfin2samsung/Downloads \
$PKG/opt/jellyfin2samsung/Assets/TizenSDB \
$PKG/opt/jellyfin2samsung/Assets/Certificate
cp jellyfin.png $PKG/usr/share/icons/hicolor/256x256/apps/jellyfin2samsung.png
cat > $PKG/usr/share/applications/jellyfin2samsung.desktop <<EOF
[Desktop Entry]
Name=Jellyfin2Samsung
Exec=/opt/jellyfin2samsung/Jellyfin2Samsung
Icon=jellyfin2samsung
Type=Application
Categories=Utility;
EOF
mkdir -p $PKG/DEBIAN
cat > $PKG/DEBIAN/control <<EOF
Package: jellyfin2samsung
Version: ${VERSION}
Architecture: amd64
Maintainer: MadeByPatrick
Description: Jellyfin2Samsung
EOF
dpkg-deb --build $PKG
mv $PKG.deb dist/${PRODUCT_NAME}-${VERSION_TAG}-linux-x64.deb
# ---------------- RELEASE (UPDATE SAME TAG EVERY COMMIT) ----------------
- uses: softprops/action-gh-release@v2
with:
tag_name: ${{ env.VERSION_TAG }}
name: ${{ env.VERSION_TAG }}
prerelease: true
overwrite_files: true
files: dist/*
body_path: RELEASE_NOTES.md
# ======================================================
# MACOS (PER-ARCH CLI + APP + DMG)
# ======================================================
macos-app:
runs-on: macos-latest
needs: beta-release
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
# ---------------- ICON ----------------
# ---------------- ICON ----------------
- name: macOS icon (SVG -> ICNS)
run: |
# Reliable SVG -> PNG conversion on GitHub macOS runners
brew install librsvg
# If the SVG is already in your repo, keep this as-is.
# If not, uncomment the curl line below and remove the local reference.
# curl -fsSL \
# https://raw.githubusercontent.com/Jellyfin2Samsung/Samsung-Jellyfin-Installer/refs/heads/master/jellyfin-tizen-logo.svg \
# -o jellyfin-tizen-logo.svg
rsvg-convert -w 1024 -h 1024 jellyfin-tizen-logo.svg -o jellyfin.png
rm -rf icon.iconset
mkdir icon.iconset
for s in 16 32 128 256 512; do
sips -z $s $s jellyfin.png --out icon.iconset/icon_${s}x${s}.png
sips -z $((s*2)) $((s*2)) jellyfin.png --out icon.iconset/icon_${s}x${s}@2x.png
done
iconutil -c icns icon.iconset -o jellyfin.icns
# ---------------- ARM64 ----------------
- name: macOS ARM64 publish
run: |
dotnet publish Jellyfin2Samsung-CrossOS/Jellyfin2Samsung.csproj \
-c Release -r osx-arm64 -p:SelfContained=true \
-o publish/osx-arm64
mkdir -p dist
tar -czf \
dist/${PRODUCT_NAME}-${{ needs.beta-release.outputs.VERSION_TAG }}-macos-arm64.tar.gz \
-C publish/osx-arm64 .
APP=Jellyfin2Samsung-arm64.app/Contents
mkdir -p $APP/MacOS $APP/Resources
cp -R publish/osx-arm64/* $APP/MacOS/
chmod +x $APP/MacOS/Jellyfin2Samsung
cp jellyfin.icns $APP/Resources/
cat > $APP/Info.plist <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key><string>Jellyfin2Samsung</string>
<key>CFBundleExecutable</key><string>Jellyfin2Samsung</string>
<key>CFBundleIdentifier</key><string>org.madebypatrick.jellyfin2samsung</string>
<key>CFBundleVersion</key><string>${{ needs.beta-release.outputs.VERSION }}</string>
<key>CFBundleShortVersionString</key><string>${{ needs.beta-release.outputs.VERSION }}</string>
<key>CFBundlePackageType</key><string>APPL</string>
<key>LSMinimumSystemVersion</key><string>11.0</string>
<key>CFBundleIconFile</key><string>jellyfin.icns</string>
</dict>
</plist>
EOF
codesign --deep --force --sign - Jellyfin2Samsung-arm64.app
rm -rf dmg-arm64
mkdir -p dmg-arm64
cp -R Jellyfin2Samsung-arm64.app dmg-arm64/Jellyfin2Samsung.app
ln -s /Applications dmg-arm64/Applications
hdiutil create \
-volname Jellyfin2Samsung \
-srcfolder dmg-arm64 \
-format UDZO \
dist/${PRODUCT_NAME}-${{ needs.beta-release.outputs.VERSION_TAG }}-macos-arm64.dmg
# ---------------- INTEL ----------------
- name: macOS Intel publish
run: |
dotnet publish Jellyfin2Samsung-CrossOS/Jellyfin2Samsung.csproj \
-c Release -r osx-x64 -p:SelfContained=true \
-o publish/osx-x64
tar -czf \
dist/${PRODUCT_NAME}-${{ needs.beta-release.outputs.VERSION_TAG }}-macos-x64.tar.gz \
-C publish/osx-x64 .
APP=Jellyfin2Samsung-x64.app/Contents
mkdir -p $APP/MacOS $APP/Resources
cp -R publish/osx-x64/* $APP/MacOS/
chmod +x $APP/MacOS/Jellyfin2Samsung
cp jellyfin.icns $APP/Resources/
cat > $APP/Info.plist <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key><string>Jellyfin2Samsung</string>
<key>CFBundleExecutable</key><string>Jellyfin2Samsung</string>
<key>CFBundleIdentifier</key><string>org.madebypatrick.jellyfin2samsung</string>
<key>CFBundleVersion</key><string>${{ needs.beta-release.outputs.VERSION }}</string>
<key>CFBundleShortVersionString</key><string>${{ needs.beta-release.outputs.VERSION }}</string>
<key>CFBundlePackageType</key><string>APPL</string>
<key>LSMinimumSystemVersion</key><string>11.0</string>
<key>CFBundleIconFile</key><string>jellyfin.icns</string>
</dict>
</plist>
EOF
codesign --deep --force --sign - Jellyfin2Samsung-x64.app
rm -rf dmg-x64
mkdir -p dmg-x64
cp -R Jellyfin2Samsung-x64.app dmg-x64/Jellyfin2Samsung.app
ln -s /Applications dmg-x64/Applications
hdiutil create \
-volname Jellyfin2Samsung \
-srcfolder dmg-x64 \
-format UDZO \
dist/${PRODUCT_NAME}-${{ needs.beta-release.outputs.VERSION_TAG }}-macos-x64.dmg
- uses: softprops/action-gh-release@v2
with:
tag_name: ${{ needs.beta-release.outputs.VERSION_TAG }}
name: ${{ needs.beta-release.outputs.VERSION_TAG }}
prerelease: true
overwrite_files: true
files: dist/*

373
.github/workflows/stable-release.yml vendored Normal file
View File

@@ -0,0 +1,373 @@
name: Stable Release
on:
pull_request:
branches: [master]
types: [closed]
workflow_dispatch:
inputs:
ref:
description: "Git ref to build (branch/tag/SHA). Default: master"
required: false
default: "master"
allow_existing_release:
description: "If true, update assets even when the stable tag already exists"
required: false
type: boolean
default: true
permissions:
contents: write
env:
PRODUCT_NAME: Jellyfin2Samsung
PROJECT_DIR: Jellyfin2Samsung-CrossOS
CONFIGURATION: Release
jobs:
# ======================================================
# STABLE RELEASE + WINDOWS + LINUX (tar.gz + deb)
# ======================================================
stable-release:
if: |
(github.event_name == 'pull_request' &&
github.event.pull_request.merged == true &&
github.event.pull_request.base.ref == 'master' &&
github.event.pull_request.head.ref == 'beta') ||
(github.event_name == 'workflow_dispatch')
runs-on: ubuntu-latest
outputs:
VERSION: ${{ steps.version.outputs.VERSION }}
VERSION_TAG: ${{ steps.version.outputs.VERSION_TAG }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.ref }}
# ---------------- VERSION (NO -beta allowed) ----------------
- name: Validate AppVersion (must be stable)
id: version
shell: bash
run: |
FILE="${PROJECT_DIR}/Helpers/AppSettings.cs"
VERSION_LINE=$(grep 'AppVersion' "$FILE" || true)
if echo "$VERSION_LINE" | grep -q '\-beta"'; then
echo "❌ AppVersion still contains '-beta'"
echo " Stable releases must use a clean version like v2.0.0.1"
exit 1
fi
VERSION=$(echo "$VERSION_LINE" | grep -oE 'v[0-9]+(\.[0-9]+){1,3}')
if [ -z "$VERSION" ]; then
echo "❌ Failed to extract stable version"
exit 1
fi
echo "VERSION=${VERSION#v}" >> "$GITHUB_ENV"
echo "VERSION_TAG=${VERSION}" >> "$GITHUB_ENV"
echo "VERSION=${VERSION#v}" >> "$GITHUB_OUTPUT"
echo "VERSION_TAG=${VERSION}" >> "$GITHUB_OUTPUT"
# --------------------------------------------------
# Guard (log-only): create or update assets
# --------------------------------------------------
- name: Stable release exists? (allow updating assets)
shell: bash
run: |
if git tag --list | grep -qx "$VERSION_TAG"; then
echo " Stable release $VERSION_TAG already exists — will update assets"
else
echo " Stable release $VERSION_TAG does not exist — will create it"
fi
# --------------------------------------------------
# Optional: in manual runs, allow user to DISABLE updating an existing release
# --------------------------------------------------
- name: Stop if release exists and manual updating is disabled
if: github.event_name == 'workflow_dispatch' && inputs.allow_existing_release == false
shell: bash
run: |
if git tag --list | grep -qx "$VERSION_TAG"; then
echo " Release $VERSION_TAG already exists and allow_existing_release=false — exiting"
exit 0
fi
# ---------------- CHANGELOG (from last stable tag) ----------------
- name: Generate changelog
shell: bash
run: |
LAST_STABLE=$(git tag \
| grep -E '^v[0-9]+(\.[0-9]+){1,3}$' \
| sort -V \
| tail -n 1)
if [ -z "$LAST_STABLE" ]; then
RANGE="HEAD"
else
RANGE="$LAST_STABLE..HEAD"
fi
{
echo "## ✅ What's New & Improved"
git log $RANGE --pretty=format:"- %s"
} > CHANGELOG.md
# ---------------- RELEASE NOTES ----------------
- name: Prepare release notes
shell: bash
run: |
DATE=$(date +'%Y-%m-%d')
{
echo "## 📦 [${VERSION_TAG}] ${DATE}"
echo ""
cat CHANGELOG.md
echo ""
echo ""
echo "| Platform | Status | Notes |"
echo "|----------|--------|-------|"
echo "| 🍎 macOS (.app + dmg) | ✅ Stable | ARM64 + Intel |"
echo "| 🍎 macOS (CLI) | ✅ Stable | Per-arch tar.gz |"
echo "| 🐧 Linux | ✅ Stable | tar.gz / .deb |"
echo "| 🪟 Windows | ✅ Stable | CI-built |"
echo ""
echo "---"
echo ""
echo "### 🛡️ Security Notice"
echo "Antivirus warnings may occur and are likely **false positives**."
} > RELEASE_NOTES.md
- uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
# ---------------- WINDOWS ----------------
- name: Build Windows
run: |
dotnet publish ${PROJECT_DIR}/Jellyfin2Samsung.csproj \
-c $CONFIGURATION -r win-x64 -p:SelfContained=true \
-o publish/win-x64
mkdir -p dist
(cd publish/win-x64 && zip -r "../../dist/${PRODUCT_NAME}-${VERSION_TAG}-win-x64.zip" .)
# ---------------- LINUX ----------------
- name: Build Linux
run: |
dotnet publish ${PROJECT_DIR}/Jellyfin2Samsung.csproj \
-c $CONFIGURATION -r linux-x64 -p:SelfContained=true \
-o publish/linux-x64
tar -czf "dist/${PRODUCT_NAME}-${VERSION_TAG}-linux-x64.tar.gz" \
-C publish/linux-x64 .
# ---------------- LINUX ICON ----------------
- name: Linux icon
run: |
sudo apt-get update
sudo apt-get install -y librsvg2-bin
rsvg-convert -w 256 -h 256 \
jellyfin-tizen-logo.svg -o jellyfin.png
# ---------------- DEB ----------------
- name: Build .deb
run: |
sudo apt-get install -y dpkg-dev
PKG=deb
mkdir -p $PKG/opt/jellyfin2samsung \
$PKG/usr/share/{applications,icons/hicolor/256x256/apps}
cp -R publish/linux-x64/. $PKG/opt/jellyfin2samsung/
chmod +x $PKG/opt/jellyfin2samsung/Jellyfin2Samsung
# 🔥 quick+dirty: allow writes where your app writes
mkdir -p \
$PKG/opt/jellyfin2samsung/Logs \
$PKG/opt/jellyfin2samsung/Downloads \
$PKG/opt/jellyfin2samsung/Assets/TizenSDB \
$PKG/opt/jellyfin2samsung/Assets/Certificate
chmod -R 777 \
$PKG/opt/jellyfin2samsung/Logs \
$PKG/opt/jellyfin2samsung/Downloads \
$PKG/opt/jellyfin2samsung/Assets/TizenSDB \
$PKG/opt/jellyfin2samsung/Assets/Certificate
cp jellyfin.png $PKG/usr/share/icons/hicolor/256x256/apps/jellyfin2samsung.png
cat > $PKG/usr/share/applications/jellyfin2samsung.desktop <<EOF
[Desktop Entry]
Name=Jellyfin2Samsung
Exec=/opt/jellyfin2samsung/Jellyfin2Samsung
Icon=jellyfin2samsung
Type=Application
Categories=Utility;
EOF
mkdir -p $PKG/DEBIAN
cat > $PKG/DEBIAN/control <<EOF
Package: jellyfin2samsung
Version: ${VERSION}
Architecture: amd64
Maintainer: MadeByPatrick
Description: Jellyfin2Samsung
EOF
dpkg-deb --build $PKG
mv $PKG.deb dist/${PRODUCT_NAME}-${VERSION_TAG}-linux-x64.deb
# ---------------- CREATE/UPDATE STABLE RELEASE ----------------
- name: Create/Update GitHub Release (stable)
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ env.VERSION_TAG }}
name: ${{ env.VERSION_TAG }}
prerelease: false
overwrite_files: true
files: dist/*
body_path: RELEASE_NOTES.md
# ======================================================
# MACOS (PER-ARCH CLI + APP + DMG)
# ======================================================
macos-app:
runs-on: macos-latest
needs: stable-release
if: needs.stable-release.result == 'success'
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.ref }}
- uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
# ---------------- ICON ----------------
- name: macOS icon
run: |
sips -s format png jellyfin-tizen-logo.svg --out jellyfin.png
sips -z 1024 1024 jellyfin.png --out jellyfin.png
mkdir icon.iconset
for s in 16 32 128 256 512; do
sips -z $s $s jellyfin.png --out icon.iconset/icon_${s}x${s}.png
sips -z $((s*2)) $((s*2)) jellyfin.png --out icon.iconset/icon_${s}x${s}@2x.png
done
iconutil -c icns icon.iconset -o jellyfin.icns
# ---------------- ARM64 ----------------
- name: macOS ARM64 publish
run: |
dotnet publish ${PROJECT_DIR}/Jellyfin2Samsung.csproj \
-c $CONFIGURATION -r osx-arm64 -p:SelfContained=true \
-o publish/osx-arm64
mkdir -p dist
tar -czf \
dist/${PRODUCT_NAME}-${{ needs.stable-release.outputs.VERSION_TAG }}-macos-arm64.tar.gz \
-C publish/osx-arm64 .
APP=Jellyfin2Samsung-arm64.app/Contents
mkdir -p $APP/MacOS $APP/Resources
cp -R publish/osx-arm64/* $APP/MacOS/
chmod +x $APP/MacOS/Jellyfin2Samsung
cp jellyfin.icns $APP/Resources/
cat > $APP/Info.plist <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key><string>Jellyfin2Samsung</string>
<key>CFBundleExecutable</key><string>Jellyfin2Samsung</string>
<key>CFBundleIdentifier</key><string>org.madebypatrick.jellyfin2samsung</string>
<key>CFBundleVersion</key><string>${{ needs.stable-release.outputs.VERSION }}</string>
<key>CFBundleShortVersionString</key><string>${{ needs.stable-release.outputs.VERSION }}</string>
<key>CFBundlePackageType</key><string>APPL</string>
<key>LSMinimumSystemVersion</key><string>11.0</string>
<key>CFBundleIconFile</key><string>jellyfin.icns</string>
</dict>
</plist>
EOF
codesign --deep --force --sign - Jellyfin2Samsung-arm64.app
rm -rf dmg-arm64
mkdir -p dmg-arm64
cp -R Jellyfin2Samsung-arm64.app dmg-arm64/Jellyfin2Samsung.app
ln -s /Applications dmg-arm64/Applications
hdiutil create \
-volname Jellyfin2Samsung \
-srcfolder dmg-arm64 \
-format UDZO \
dist/${PRODUCT_NAME}-${{ needs.stable-release.outputs.VERSION_TAG }}-macos-arm64.dmg
# ---------------- INTEL ----------------
- name: macOS Intel publish
run: |
dotnet publish ${PROJECT_DIR}/Jellyfin2Samsung.csproj \
-c $CONFIGURATION -r osx-x64 -p:SelfContained=true \
-o publish/osx-x64
tar -czf \
dist/${PRODUCT_NAME}-${{ needs.stable-release.outputs.VERSION_TAG }}-macos-x64.tar.gz \
-C publish/osx-x64 .
APP=Jellyfin2Samsung-x64.app/Contents
mkdir -p $APP/MacOS $APP/Resources
cp -R publish/osx-x64/* $APP/MacOS/
chmod +x $APP/MacOS/Jellyfin2Samsung
cp jellyfin.icns $APP/Resources/
cat > $APP/Info.plist <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key><string>Jellyfin2Samsung</string>
<key>CFBundleExecutable</key><string>Jellyfin2Samsung</string>
<key>CFBundleIdentifier</key><string>org.madebypatrick.jellyfin2samsung</string>
<key>CFBundleVersion</key><string>${{ needs.stable-release.outputs.VERSION }}</string>
<key>CFBundleShortVersionString</key><string>${{ needs.stable-release.outputs.VERSION }}</string>
<key>CFBundlePackageType</key><string>APPL</string>
<key>LSMinimumSystemVersion</key><string>11.0</string>
<key>CFBundleIconFile</key><string>jellyfin.icns</string>
</dict>
</plist>
EOF
codesign --deep --force --sign - Jellyfin2Samsung-x64.app
rm -rf dmg-x64
mkdir -p dmg-x64
cp -R Jellyfin2Samsung-x64.app dmg-x64/Jellyfin2Samsung.app
ln -s /Applications dmg-x64/Applications
hdiutil create \
-volname Jellyfin2Samsung \
-srcfolder dmg-x64 \
-format UDZO \
dist/${PRODUCT_NAME}-${{ needs.stable-release.outputs.VERSION_TAG }}-macos-x64.dmg
# ---------------- UPLOAD MACOS ASSETS TO SAME STABLE RELEASE ----------------
- name: Upload macOS assets to GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ needs.stable-release.outputs.VERSION_TAG }}
name: ${{ needs.stable-release.outputs.VERSION_TAG }}
prerelease: false
overwrite_files: true
files: dist/*

View File

@@ -3,8 +3,18 @@ on:
schedule:
- cron: "0 */6 * * *"
workflow_dispatch:
workflow_run:
workflows: ["Beta Pre-Release"]
types:
- completed
jobs:
update-readme:
if: |
github.event_name != 'workflow_run' ||
(
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.head_branch == 'beta'
)
runs-on: ubuntu-latest
permissions:
contents: write
@@ -12,6 +22,7 @@ jobs:
- uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
ref: beta
- name: Fetch latest releases
id: releases
uses: actions/github-script@v7
@@ -52,7 +63,6 @@ jobs:
|------------|---------------------------------------------------------------------|------------------------------|
| **Stable** | [$STABLE_TAG]($STABLE_URL) | Recommended for most users |
| **Beta** | [$BETA_TAG]($BETA_URL) | Includes new features |
| **Legacy** | [v1.7.9](https://github.com/PatrickSt1991/Samsung-Jellyfin-Installer/releases/tag/v1.7.9) | Legacy Win x64 version |
EOF
- name: Inject version table into README
run: |

1
.gitignore vendored
View File

@@ -178,7 +178,6 @@ DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml

View File

@@ -1,7 +1,7 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Jellyfin2SamsungCrossOS.App"
xmlns:local="using:Jellyfin2SamsungCrossOS"
x:Class="Jellyfin2Samsung.App"
xmlns:local="using:Jellyfin2Samsung"
RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
@@ -12,7 +12,7 @@
<Application.Styles>
<FluentTheme />
<Style Selector="Window">
<Setter Property="Icon" Value="avares://Jellyfin2SamsungCrossOS/Assets/jelly2sams.ico" />
<Setter Property="Icon" Value="avares://Jellyfin2Samsung/Assets/jelly2sams.ico" />
</Style>
</Application.Styles>
</Application>

View File

@@ -3,25 +3,29 @@ using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Data.Core.Plugins;
using Avalonia.Markup.Xaml;
using Avalonia.Styling;
using Jellyfin2SamsungCrossOS.Extensions;
using Jellyfin2SamsungCrossOS.Helpers;
using Jellyfin2SamsungCrossOS.Services;
using Jellyfin2SamsungCrossOS.ViewModels;
using Jellyfin2SamsungCrossOS.Views;
using Jellyfin2Samsung.Extensions;
using Jellyfin2Samsung.Helpers;
using Jellyfin2Samsung.Helpers.API;
using Jellyfin2Samsung.Helpers.Core;
using Jellyfin2Samsung.Helpers.Jellyfin;
using Jellyfin2Samsung.Helpers.Jellyfin.Plugins;
using Jellyfin2Samsung.Helpers.Tizen.Certificate;
using Jellyfin2Samsung.Helpers.Tizen.Devices;
using Jellyfin2Samsung.Interfaces;
using Jellyfin2Samsung.Services;
using Jellyfin2Samsung.ViewModels;
using Jellyfin2Samsung.Views;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
namespace Jellyfin2SamsungCrossOS
namespace Jellyfin2Samsung
{
public partial class App : Application
{
private IServiceProvider _serviceProvider;
// Static property to access services from anywhere
public static IServiceProvider Services { get; private set; }
public override void Initialize()
@@ -29,7 +33,7 @@ namespace Jellyfin2SamsungCrossOS
AvaloniaXamlLoader.Load(this);
}
public async override void OnFrameworkInitializationCompleted()
public override void OnFrameworkInitializationCompleted()
{
ConfigureServices();
@@ -37,135 +41,112 @@ namespace Jellyfin2SamsungCrossOS
{
DisableAvaloniaDataAnnotationValidation();
if (!OperatingSystem.IsWindows())
{
await RequestInitialPrivilegesWithUI();
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
{
var mainWindow = _serviceProvider.GetRequiredService<MainWindow>();
desktop.MainWindow = mainWindow;
mainWindow.Show();
});
}
else
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
{
var mainWindow = _serviceProvider.GetRequiredService<MainWindow>();
desktop.MainWindow = mainWindow;
}
mainWindow.Show();
});
}
RequestedThemeVariant = ThemeVariant.Light;
// Apply saved theme on startup
var themeService = _serviceProvider.GetRequiredService<IThemeService>();
themeService.ApplyTheme();
base.OnFrameworkInitializationCompleted();
}
private async Task RequestInitialPrivilegesWithUI()
{
try
{
var dialogService = _serviceProvider.GetRequiredService<IDialogService>();
await dialogService.ShowMessageAsync(
"Administrator Privileges Required",
"This application needs administrator privileges to install software on your system. " +
"You will be prompted for your password once at the beginning.");
// Request privileges
var processHelper = _serviceProvider.GetRequiredService<ProcessHelper>();
await RequestInitialPrivileges(processHelper);
}
catch (Exception ex)
{
// Handle any errors silently or log them
Console.WriteLine($"Privilege request failed: {ex.Message}");
}
}
private async Task<bool> RequestInitialPrivileges(ProcessHelper processHelper)
{
try
{
if (OperatingSystem.IsLinux())
{
// Use pkexec to authenticate, then extend sudo timeout
var result1 = await processHelper.RunCommandAsync("pkexec", "sudo -v");
if (result1.ExitCode == 0)
{
// Extend the sudo timeout to maximum (usually 15 minutes)
var result2 = await processHelper.RunCommandAsync("sudo", "-v");
return result2.ExitCode == 0;
}
return false;
}
else if (OperatingSystem.IsMacOS())
{
// Test macOS authentication
var result = await processHelper.RunCommandAsync("osascript",
"-e \"do shell script \\\"true\\\" with administrator privileges\"");
return result.ExitCode == 0;
}
}
catch (Exception ex)
{
Console.WriteLine($"Authentication error: {ex.Message}");
return false;
}
return false;
}
private void ConfigureServices()
{
var services = new ServiceCollection();
var settings = AppSettings.Load();
Debug.WriteLine($"LANG SETUP: {settings.Language}");
// Services
services.AddSingleton<AppSettings>(settings);
// --------------------
// Core services
// --------------------
services.AddSingleton(settings);
services.AddSingleton<IDialogService, DialogService>();
services.AddSingleton<ILocalizationService, LocalizationService>();
services.AddSingleton<INetworkService, NetworkService>();
services.AddSingleton<ITizenCertificateService, TizenCertificateService>();
services.AddSingleton<ITizenInstallerService, TizenInstallerService>();
services.AddSingleton<SamsungLoginService>();
services.AddSingleton<HttpClient>();
services.AddSingleton<IThemeService, ThemeService>();
services.AddSingleton<IUpdaterService, UpdaterService>();
services.AddSingleton<IUpdateDialogService, UpdateDialogService>();
// HttpClient (configured ONCE, with GitHub auth if available)
services.AddSingleton(sp =>
{
var appSettings = sp.GetRequiredService<AppSettings>();
var token = Helpers.Core.GitHubAuthHandler.ResolveToken(appSettings);
var handler = new Helpers.Core.GitHubAuthHandler(token);
var client = new HttpClient(handler)
{
Timeout = TimeSpan.FromSeconds(30)
};
client.DefaultRequestHeaders.UserAgent.ParseAdd("SamsungJellyfinInstaller/1.1");
client.DefaultRequestHeaders.Accept.ParseAdd("application/vnd.github+json");
return client;
});
services.AddSingleton<SamsungLoginService>();
services.AddSingleton<JellyfinApiClient>();
services.AddSingleton<TizenApiClient>();
services.AddSingleton<PluginManager>();
services.AddSingleton<JellyfinPackagePatcher>();
// --------------------
// Helpers
// --------------------
services.AddSingleton<DeviceHelper>();
services.AddSingleton<PackageHelper>();
services.AddSingleton<JellyfinHelper>();
services.AddSingleton<CertificateHelper>();
services.AddSingleton<FileHelper>();
services.AddSingleton<OperatingSystemHelper>();
services.AddSingleton<ProcessHelper>();
services.AddSingleton<TvLogService>();
// --------------------
// ViewModels
services.AddSingleton<MainWindowViewModel>();
services.AddSingleton<SettingsViewModel>();
// --------------------
services.AddTransient<MainWindowViewModel>();
services.AddTransient<InstallationCompleteViewModel>();
services.AddTransient<InstallingWindowViewModel>();
services.AddTransient<TvLogsViewModel>();
services.AddSingleton<JellyfinConfigViewModel>();
// JellyfinConfigViewModel requires JellyfinHelper
services.AddTransient<JellyfinConfigViewModel>(provider =>
{
var helper = provider.GetRequiredService<JellyfinHelper>();
var localization = provider.GetRequiredService<ILocalizationService>();
return new JellyfinConfigViewModel(helper, localization);
});
// --------------------
// Views
services.AddSingleton<MainWindow>(provider =>
// --------------------
services.AddSingleton(provider =>
{
return new MainWindow
var vm = provider.GetRequiredService<MainWindowViewModel>();
var window = new MainWindow
{
DataContext = provider.GetRequiredService<MainWindowViewModel>()
DataContext = vm
};
// IMPORTANT: prevent memory leak
window.Closed += (_, _) =>
{
if (vm is IDisposable d)
d.Dispose();
};
return window;
});
services.AddTransient<JellyfinConfigView>(provider =>
services.AddTransient(provider =>
{
var vm = provider.GetRequiredService<JellyfinConfigViewModel>();
return new JellyfinConfigView(vm);
});
services.AddTransient<InstallingWindow>(provider =>
services.AddTransient(provider =>
{
var vm = provider.GetRequiredService<InstallingWindowViewModel>();
return new InstallingWindow
@@ -174,17 +155,19 @@ namespace Jellyfin2SamsungCrossOS
};
});
services.AddTransient<InstallationCompleteWindow>(provider =>
services.AddTransient(provider =>
{
var vm = provider.GetRequiredService<InstallationCompleteViewModel>();
return new InstallationCompleteWindow(vm);
});
// Build and assign service provider
// --------------------
// Build provider
// --------------------
_serviceProvider = services.BuildServiceProvider();
Services = _serviceProvider;
// Set localization service globally
// Localization bootstrap
var localizationService = _serviceProvider.GetRequiredService<ILocalizationService>();
LocalizationExtensions.SetLocalizationService(localizationService);
}
@@ -192,7 +175,9 @@ namespace Jellyfin2SamsungCrossOS
private void DisableAvaloniaDataAnnotationValidation()
{
var dataValidationPluginsToRemove =
BindingPlugins.DataValidators.OfType<DataAnnotationsValidationPlugin>().ToArray();
BindingPlugins.DataValidators
.OfType<DataAnnotationsValidationPlugin>()
.ToArray();
foreach (var plugin in dataValidationPluginsToRemove)
BindingPlugins.DataValidators.Remove(plugin);

View File

@@ -0,0 +1 @@
tizenpkcs12passfordsigner

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -1,58 +1,37 @@
{
"DownloadAndInstall": "Download & Installer",
"InstallationFailed": "Installation mislykkedes",
"InstallationSuccessfulOn": "Jellyfin er blevet installeret!",
"InstallationSuccessfulOn": "{0} er blevet installeret!",
"DownloadFailed": "Download mislykkedes:",
"FailedLoadingReleases": "Kunne ikke indlæse udgivelser:",
"PleaseInstallTizen": "Tizen CLI er påkrævet, men ikke fundet. Installer venligst Tizen Studio først.",
"DownloadingPackage": "Downloader pakke...",
"InstallTizenSdb": "Tizen SDB er påkrævet, men ikke fundet. Prøv at downloade igen.",
"FailedTizenSdb": "Tizen SDB er påkrævet, men kunne ikke findes og downloades.",
"ConnectingToDevice": "Opretter forbindelse til enhed...",
"RetrievingDeviceAddress": "Henter enhedsadresse...",
"TvNameNotFound": "TV-navn",
"TvDuidNotFound": "TV-duid kunne ikke findes...",
"UpdatingCertificateProfile": "Opdaterer certifikatprofil...",
"CreatingCertificateProfile": "Opretter ny certifikatprofil...",
"PackagingWgtWithCertificate": "Pakker wgt-filen med certifikat...",
"InstallationSuccessful": "Installation gennemført",
"InstallingPackage": "Installerer pakke på enhed...",
"InstallationMaybeFailed": "Installationen kan være mislykkedes",
"Output": "Output",
"LoadingReleases": "Indlæser udgivelser...",
"ScanningNetwork": "Søger i netværk efter Samsung TV...",
"CheckTizenOS": "Checker Tizen OS-version...",
"SuccessAuthCode": "Autorisationskode modtaget",
"Ready": "Klar til brug...",
"SamsungLogin": "Logerer ind på Samsung...",
"FailedAuthCode": "Login blev annulleret eller mislykkedes...",
"NoDevicesFound": "Ingen enheder fundet",
"OutputDir": "Sikrer at outputmappen eksisterer",
"GenPassword": "Genererer tilfældig adgangskode...",
"GenKeyPair": "Genererer nøglepar",
"CreateAuthorCsr": "Opretter Author CSR...",
"CreateDistributorCSR": "Genererer Distributor CSR med DUID...",
"PostAuthorCSR": "Sender til Samsung author endpoint...",
"PostDistributorCSR": "Sender til Samsung distributor...",
"CreateNewCertificates": "Opretter de signerede P12-filer...",
"ExtractRootCertificate": "Udpakker rodcertifikater...",
"ExportPfxCertificates": "Eksporterer PFX-certifikater...",
"MovingP12Files": "Kopierer filer til brug i certificate-manager...",
"SettingCertificateManager": "Retter Tizen certificate manager...",
"SettingsCaCerts": "Indstiller CA-certifikater...",
"ChooseRelease": "Vælg udgivelse...",
"ChooseVersion": "Vælg version...",
"ChooseTV": "Vælg TV...",
"SelectLanguage": "Vælg sprog",
"SelectCertificate": "Vælg certifikat",
"SelectWGT": "Vælg WGT og/eller TPK-fil(er)",
"lblRelease": "Udgivelse",
"lblVersion": "Version",
"lblSelectTv": "Vælg TV",
"lblLanguage": "Sprog",
"lblCustomWgt": "Brugerdefineret WGT",
"lblSettings": "Indstillinger",
"lblRememberIp": "Husk IP'er",
"lblCustomWgt": "WGT-fil",
"lblSettings": "Applikationsindstillinger",
"lblDeletePrevious": "Fjern gamle Jellyfin",
"lblexpires": "Udløber",
"IpWindowTitle": "Indtast TV IP",
"IpWindowDescription": "Indtast venligst enhedens IP-adresse:",
"InvalidDeviceIp": "Ugyldig enheds-IP eller enhed ikke fundet.",
@@ -63,34 +42,15 @@
"NoPackageToInstall": "Ingen pakke at installere",
"NoDeviceSelected": "Ingen enhed valgt",
"UsingCustomWGT": "Bruger brugerdefineret WGT-fil",
"FailedRemoveOld": "Kunne ikke fjerne gammel app-version",
"FailedRemoveOldExtra": "Fjern venligst den gamle Jellyfin-app manuelt fra TV'et",
"CheckingTizenCli": "Checker Tizen CLI...",
"TizenCliFailed": "Tizen CLI-installation mislykkedes...",
"CheckingTizenSdb": "Checker Tizen SDB...",
"InitializationFailed": "Initialisering mislykkedes...",
"DownloadingSetupFile": "Downloader installer...",
"InstallingSetupFile": "Installerer Tizen Studio CLI...",
"InstallingCertificateManager": "Installerer Certificate Manager...",
"ReInstallingCertificateManager": "Installation af Certifikathåndtering-tilføjelsen mislykkedes. Vil du prøve igen?",
"InstallingCertificateAddOn": "Installerer Certificate Manager add-on...",
"CheckingPackageManagerList": "Checker Tizen Package manager",
"PermissionDenied": "Adgang nægtet",
"AdminPrivRequired": "Administratorrettigheder er påkrævet for at oprette mappen. Opret den manuelt eller kør programmet som Administrator.",
"PathLengthWarning": "Windows-stilængde overskredet",
"PathLengthExceeded": "Standardstien overskrider Windows-stilængden. Vil du ændre den til C:\\Tizen Studio Cli?",
"lblForceLogin": "Tving Samsung Login",
"lblForceLogin": "Tving Samsung-certifikat",
"DeveloperModeRequired": "Samsung TV er ikke i developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP matcher ikke denne enheds lokale IP, vil du fortsætte?",
"lblRTL": "Højre-til-venstre (RTL) læsning",
"FailedToStopSdbServer": "Der kører allerede en SDB Server, kunne ikke stoppe...",
"FailedToStopSdbServerTitle": "SDB Server kunne ikke stoppe!",
"lblJellyfinServer": "Adresse",
"lblModifyConfig": "Indstil Jellyfin Config",
"ConfigFailure": "config.json ikke fundet i den udpakkede pakke.",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Omvendt IP (for arabisk/hebraisk)",
"ServerIP": "Server IP",
"ServerPort": "Port",
"Theme": "Tema",
"SelectTheme": "Vælg tema",
"lblEnableBackdrops": "Aktiver baggrunde",
"lblEnableThemeSongs": "Aktiver temasange",
"lblEnableThemeVideos": "Aktiver temavideoer",
@@ -100,31 +60,13 @@
"lblNextUpEnabled": "Næste aktiveret",
"lblEnableExternalVideoPlayers": "Aktiver eksterne videomedspillere",
"lblSkipIntros": "Spring intros over",
"lblAutoPlayNextEpisode": "Automatisk afspil næste afsnit",
"lblRememberAudioSelections": "Husk lydvalg",
"lblRememberSubtitleSelections": "Husk undertekstvalg",
"lblPlayDefaultAudioTrack": "Afspil standard lydspor",
"lblSubtitleMode": "Underteksttilstand",
"lblAudioLanguagePreference": "Foretrukket lydsprog",
"lblSubtitleLanguagePreference": "Foretrukket undertekstsprog",
"lblJellyfinConfig": "Jellyfin Config",
"SelectSubtitleMode": "Vælg underteksttilstand",
"lblJellyfinConfig": "Jellyfin-indstillinger",
"AudioLanguage": "f.eks. da | en | de",
"lblOpenConfig": "Åbn config",
"lblValidateLogin": "Valider",
"lblValidateJellyfin": "Valider Jellyfin Server",
"NoJellyfinServer": "Udfyld venligst Jellyfin serveren først...",
"JellyfinLoginFailed": "Login mislykkedes: Ugyldige legitimationsoplysninger eller serverfejl.",
"lblJellyfinServerApi": "API-nøgle",
"UpdateMode": "Opdateringstilstand",
"lblServerSettings": "Serverindstillinger",
"lblBrowserSettings": "Browserindstillinger",
"lblUserSettings": "Brugerindstillinger",
"lbluserAutoLogin": "Aktiver automatisk login",
"lblJellyfinUser": "Jellyfin bruger(e)",
"lblFailedUsers": "Kunne ikke hente nogen Jellyfin-brugere, ingen Config-indstillinger vil blive ændret!",
"lblUpdateMode": "Vælg hvad der skal opdateres",
"lblJellyfinAPIKey": "Jellyfin API-nøgle",
"lblSelectUsers": "Vælg bruger(e) der skal opdateres",
"lblValidation": "🍺 Køb mig en øl",
"btn_Close": "Luk",
@@ -136,9 +78,78 @@
"keyContinue": "Fortsætte",
"keyStop": "Stop",
"keyConfirm": "Bekræfte",
"minimalCliTitle": "Tizen CLI og certifikatadministrator kræves",
"minimalCliMessage": "Tizen CLI og certifikatadministrator er nødvendige for at fortsætte.\n\nVi downloader og installerer nu Tizen CLI efterfulgt af certifikatadministrator.\nDette kan tage et par minutter. Vær tålmodig under installationsprocessen.",
"minimalCliStop": "Brugeren afviste at installere Tizen CLI",
"packageAndSign": "Emballering og signering...",
"certRetryFailed": "Gentagelse mislykkedes, opret venligst en fejl"
"alreadyInstalled": "Jellyfin kunne ikke installeres, da den allerede er installeret. Fjern den først, og prøv igen.",
"diagnoseTv": "Diagnosticér tv-funktioner",
"modiyConfigRequired": "Jellyfin-appen har brug for et nyt app-id!",
"deleteExistingVersion": "Sletter en eksisterende version...",
"deleteExistingFailed": "Sletning af den eksisterende version mislykkedes. Slet den manuelt...",
"deleteExistingSuccess": "Eksisterende version slettet...",
"deleteExistingNotAllowed": "Sletning er ikke tilladt. Aktivér indstillingen for at tillade værktøjet at fjerne den eksisterende app...",
"lblLocalIP": "Lokal IP:",
"lblTryOverwrite": "Overskriv eksisterende version",
"lblUseServerScripts": "Download Jellyfin-plugins",
"lblEnableDevLogs": "Aktivér tv-fejlfinding",
"lblOpenDebugWindow": "Åbn TV-log",
"lblStartLog": "Starte",
"lblStopLog": "Stop",
"lblSaveLogs": "Spare",
"lblLaunchOnInstall": "Åbn efter installation",
"insufficientSpace": "Din enhed har ikke nok plads. Fjern nogle apps og prøv igen...",
"lblKeepWGTFile": "Bevar WGT-fil",
"AuthorMismatch": "Forfattercertifikat stemmer ikke overens. Underskriv venligst pakken igen med det korrekte certifikat.",
"FixYouTube153": "Ret YouTube-plugin (fejl 153)",
"lblBasePath": "Basissti",
"lblJellyfinUsername": "Brugernavn",
"lblJellyfinPassword": "Adgangskode",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-login indstillinger",
"lblBasePathHint": "Tip: Indsæt fuld URL (f.eks. https://host.com/sti/jellyfin) for at udfylde alle felter automatisk",
"lblTestServer": "Test Server",
"lblLogout": "Log ud",
"lblEnableAutoLoginConfig": "Aktivér config-patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Afspilning",
"lblServerInputMode": "Inputtilstand",
"lblServerUrl": "Server-URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Avancerede indstillinger",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Stil",
"lblCssSettings": "Brugerdefinerede CSS-indstillinger",
"lblCustomCss": "Brugerdefineret CSS-kode",
"lblCssHint": "Indtast CSS eller brug @import til at indlæse eksterne temaer",
"lblValidateCss": "Valider CSS",
"lblCssValidationStatus": "Valideringsstatus",
"lblCssEmpty": "Ingen CSS at validere",
"lblClearCss": "Ryd",
"lblCssValidating": "Validerer...",
"lblCssUrlFailed": "{0} URL(er) ikke tilgængelige",
"lblCssUrlsValid": "{0} URL(er) valideret med succes",
"lblCssSyntaxValid": "CSS-syntaks gyldig",
"lblCssUnmatchedBrace": "Ulige klammer { }",
"lblCssUnmatchedParen": "Ulige parenteser ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Klik på et tema for at indsætte det og automatisk validere. Besøg GitHub-repo for forhåndsvisninger.",
"lblTabMainSettings": "Hoved",
"lblMainSettings": "Programindstillinger",
"lblRefreshUsers": "Opdater brugere",
"lblUserSelectionHint": "Valgte brugere konfigureres under installationen",
"subnetMismatch": "Enhederne er i forskellige undernetværk (netværk)",
"UpdateAvailable": "Opdatering tilgængelig",
"UpdateCurrentVersion": "Nuværende version:",
"UpdateLatestVersion": "Nyeste version:",
"UpdateReleaseNotes": "Udgivelsesnoter:",
"UpdateManual": "Åbn udgivelser",
"UpdateAutomatic": "Opdater nu",
"UpdateSkip": "Spring denne version over",
"UpdateDownloading": "Downloader opdatering...",
"UpdateApplying": "Anvender opdatering",
"UpdateApplyingMessage": "Applikationen genstarter for at fuldføre opdateringen.",
"UpdateError": "Opdatering mislykkedes",
"UpdateCheckFailed": "Kunne ikke tjekke for opdateringer",
"IncompatiblePackage": "Pakkeversionen er ikke kompatibel med enheden.",
"IncompatiblePackageDetailed": "Pakkeversion {0} er ikke kompatibel med Tizen OS {1}",
"lblMdnsWarning": "Advarsel: Du bruger et mDNS-hostnavn (.local). Samsung TV'er kan ikke pålideligt opløse .local-adresser, hvilket kan få serveren til at vises som \"undefined\" på dit TV — især efter netværksafbrydelser. Brug en direkte IP-adresse (f.eks. 192.168.1.100) i stedet for en stabil forbindelse."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Herunterladen & Installieren",
"InstallationFailed": "Installation fehlgeschlagen",
"InstallationSuccessfulOn": "{0} wurde erfolgreich installiert!",
"DownloadFailed": "Download fehlgeschlagen:",
"FailedLoadingReleases": "Laden der Versionen fehlgeschlagen:",
"InstallTizenSdb": "Das Tizen SDB ist erforderlich, wurde aber nicht gefunden. Download wird erneut versucht.",
"FailedTizenSdb": "Tizen SDB ist erforderlich, konnte aber nicht gefunden und heruntergeladen werden.",
"ConnectingToDevice": "Verbindung zum Gerät wird hergestellt...",
"TvNameNotFound": "TV-Name konnte nicht gefunden werden...",
"TvDuidNotFound": "TV-DUID konnte nicht gefunden werden...",
"CreatingCertificateProfile": "Neues Zertifikatsprofil wird erstellt...",
"InstallationSuccessful": "Installation erfolgreich",
"InstallingPackage": "Paket wird auf Gerät installiert...",
"Output": "Ausgabe",
"LoadingReleases": "Versionen werden geladen...",
"ScanningNetwork": "Netzwerk wird nach Samsung TV durchsucht...",
"Ready": "Bereit zur Nutzung...",
"NoDevicesFound": "Keine Geräte gefunden",
"GenPassword": "Zufälliges Passwort wird generiert...",
"GenKeyPair": "Schlüsselpaar wird generiert",
"CreateAuthorCsr": "Autor-CSR wird erstellt...",
"CreateDistributorCSR": "Distributor-CSR wird mit DUID generiert...",
"PostAuthorCSR": "An Samsung-Autor-Endpunkt wird gesendet...",
"PostDistributorCSR": "An Samsung-Distributor wird gesendet...",
"ExportPfxCertificates": "PFX-Zertifikate werden exportiert...",
"SelectWGT": "WGT- und/oder TPK-Datei(en) auswählen",
"lblRelease": "Version",
"lblVersion": "Version",
"lblSelectTv": "TV auswählen",
"lblLanguage": "Sprache",
"lblCustomWgt": "WGT-Datei",
"lblSettings": "Anwendungseinstellungen",
"lblDeletePrevious": "Altes Jellyfin entfernen",
"IpWindowTitle": "TV-IP eingeben",
"IpWindowDescription": "Bitte die IP-Adresse des Geräts eingeben:",
"InvalidDeviceIp": "Ungültige Geräte-IP oder Gerät nicht gefunden.",
"IpNotListed": "Meine IP ist nicht aufgeführt...",
"lblCertifcate": "Zertifikat",
"lblOther": "Andere",
"DownloadCompleted": "Download abgeschlossen...",
"NoPackageToInstall": "Kein Paket zu installieren",
"NoDeviceSelected": "Kein Gerät ausgewählt",
"UsingCustomWGT": "Benutzerdefinierte WGT-Datei wird verwendet",
"CheckingTizenSdb": "Tizen SDB wird überprüft...",
"InitializationFailed": "Initialisierung fehlgeschlagen...",
"lblForceLogin": "Samsung-Zertifikat erzwingen",
"DeveloperModeRequired": "Samsung TV ist nicht im Entwicklermodus...",
"DeveloperIPMismatch": "Samsung Developer Mode IP stimmt nicht mit der lokalen IP dieses Geräts überein, möchten Sie fortfahren?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (für Arabisch/Hebräisch)",
"ServerIP": "Server-IP",
"Theme": "Theme",
"lblEnableBackdrops": "Hintergrundbilder aktivieren",
"lblEnableThemeSongs": "Theme-Songs aktivieren",
"lblEnableThemeVideos": "Theme-Videos aktivieren",
"lblBackdropScreensaver": "Hintergrund-Bildschirmschoner",
"lblDetailsBanner": "Details-Banner",
"lblCinemaMode": "Kino-Modus",
"lblNextUpEnabled": "Als Nächstes aktiviert",
"lblEnableExternalVideoPlayers": "Externe Videoplayer aktivieren",
"lblSkipIntros": "Intros überspringen",
"lblSubtitleMode": "Untertitelmodus",
"lblAudioLanguagePreference": "Audiosprache-Präferenz",
"lblSubtitleLanguagePreference": "Untertitelsprache-Präferenz",
"lblJellyfinConfig": "Jellyfin-Einstellungen",
"AudioLanguage": "z.B. de | en | fr",
"lblServerSettings": "Servereinstellungen",
"lblBrowserSettings": "Browser-Einstellungen",
"lblSelectUsers": "Benutzer zum Aktualisieren auswählen",
"lblValidation": "🍺 Kauf mir ein Bier",
"btn_Close": "Schließen",
"lbleasyRight": "Das war einfach, oder?",
"NoDevicesFoundRetry": "Keine Geräte gefunden, mit virtuellem NIC erneut versuchen?",
"RetySearchMsg": "Keine TVs gefunden, möchten Sie erneut suchen, einschließlich virtueller Netzwerkkarten?",
"keyYes": "Ja",
"keyNo": "Nein",
"keyContinue": "Weiter",
"keyStop": "Stopp",
"keyConfirm": "Bestätigen",
"packageAndSign": "Verpacken und Signieren...",
"alreadyInstalled": "Jellyfin konnte nicht installiert werden, da es bereits installiert ist. Bitte entfernen Sie es zuerst und versuchen Sie es erneut...",
"diagnoseTv": "TV-Fähigkeiten diagnostizieren",
"modiyConfigRequired": "Jellyfin-App benötigt eine neue App-ID!",
"deleteExistingVersion": "Existierende Version wird gelöscht...",
"deleteExistingFailed": "Löschen der existierenden Version fehlgeschlagen. Bitte löschen Sie sie manuell...",
"deleteExistingSuccess": "Existierende Version erfolgreich gelöscht...",
"deleteExistingNotAllowed": "Löschen nicht erlaubt. Aktivieren Sie die Einstellung, um dem Tool zu erlauben, die existierende App zu entfernen...",
"lblLocalIP": "Lokale IP:",
"lblTryOverwrite": "Vorhandene Version überschreiben",
"lblUseServerScripts": "Jellyfin-Plugins herunterladen",
"lblEnableDevLogs": "TV-Debugging aktivieren",
"lblOpenDebugWindow": "TV-Log öffnen",
"lblStartLog": "Start",
"lblStopLog": "Stopp",
"lblSaveLogs": "Speichern",
"lblLaunchOnInstall": "Nach der Installation öffnen",
"insufficientSpace": "Auf Ihrem Gerät ist nicht genügend Speicherplatz vorhanden. Bitte entfernen Sie einige Apps und versuchen Sie es erneut ...",
"lblKeepWGTFile": "WGT-Datei beibehalten",
"AuthorMismatch": "Das Autorenzertifikat stimmt nicht überein. Bitte signieren Sie das Paket erneut mit dem richtigen Zertifikat.",
"FixYouTube153": "YouTube-Plugin reparieren (Fehler 153)",
"lblBasePath": "Basispfad",
"lblJellyfinUsername": "Benutzername",
"lblJellyfinPassword": "Passwort",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login-Einstellungen",
"lblBasePathHint": "Tipp: Vollständige URL einfügen (z.B. https://host.com/pfad/jellyfin) um alle Felder automatisch auszufüllen",
"lblTestServer": "Server testen",
"lblLogout": "Abmelden",
"lblEnableAutoLoginConfig": "Config-Patching für Auto-Login aktivieren",
"lblTabServer": "Server",
"lblTabPlayback": "Wiedergabe",
"lblServerInputMode": "Eingabemodus",
"lblServerUrl": "Server-URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Erweiterte Einstellungen",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Benutzerdefinierte CSS-Einstellungen",
"lblCustomCss": "Benutzerdefinierter CSS-Code",
"lblCssHint": "CSS eingeben oder @import für externe Themes verwenden",
"lblValidateCss": "CSS validieren",
"lblCssValidationStatus": "Validierungsstatus",
"lblCssEmpty": "Kein CSS zum Validieren",
"lblClearCss": "Leeren",
"lblCssValidating": "Wird validiert...",
"lblCssUrlFailed": "{0} URL(s) nicht erreichbar",
"lblCssUrlsValid": "{0} URL(s) erfolgreich validiert",
"lblCssSyntaxValid": "CSS-Syntax gültig",
"lblCssUnmatchedBrace": "Ungleiche Klammern { }",
"lblCssUnmatchedParen": "Ungleiche Klammern ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Klicken Sie auf ein Theme um es einzufügen und automatisch zu validieren. Besuchen Sie das GitHub-Repo für Vorschauen.",
"lblTabMainSettings": "Haupt",
"lblMainSettings": "Anwendungseinstellungen",
"lblRefreshUsers": "Benutzer aktualisieren",
"lblUserSelectionHint": "Ausgewählte Benutzer werden bei der Installation konfiguriert",
"subnetMismatch": "Die Geräte befinden sich in unterschiedlichen Subnetzen (Netzwerken).",
"UpdateAvailable": "Update verfügbar",
"UpdateCurrentVersion": "Aktuelle Version:",
"UpdateLatestVersion": "Neueste Version:",
"UpdateReleaseNotes": "Versionshinweise:",
"UpdateManual": "Releases öffnen",
"UpdateAutomatic": "Jetzt aktualisieren",
"UpdateSkip": "Diese Version überspringen",
"UpdateDownloading": "Update wird heruntergeladen...",
"UpdateApplying": "Update wird angewendet",
"UpdateApplyingMessage": "Die Anwendung wird neu gestartet, um das Update abzuschließen.",
"UpdateError": "Update fehlgeschlagen",
"UpdateCheckFailed": "Update-Überprüfung fehlgeschlagen",
"IncompatiblePackage": "Paketversion ist nicht mit dem Gerät kompatibel.",
"IncompatiblePackageDetailed": "Paketversion {0} nicht kompatibel mit Tizen OS {1}",
"lblMdnsWarning": "Warnung: Du verwendest einen mDNS-Hostnamen (.local). Samsung TVs können .local-Adressen nicht zuverlässig auflösen, was dazu führen kann, dass der Server auf deinem TV als \"undefined\" angezeigt wird — besonders nach Netzwerkunterbrechungen. Verwende stattdessen eine direkte IP-Adresse (z.B. 192.168.1.100) für eine stabile Verbindung."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -1,58 +1,37 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "Jellyfin has been successfully installed!",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"PleaseInstallTizen": "Tizen CLI is required but not found. Please install Tizen Studio first.",
"DownloadingPackage": "Downloading package...",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"RetrievingDeviceAddress": "Retrieving device address...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"UpdatingCertificateProfile": "Updating certificate profile...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"PackagingWgtWithCertificate": "Packaging the wgt file with certificate...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"InstallationMaybeFailed": "Installation may have failed",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"CheckTizenOS": "Checking Tizen OS version...",
"SuccessAuthCode": "Successfully obtained authorization code",
"Ready": "Ready for use...",
"SamsungLogin": "Signing into Samsung...",
"FailedAuthCode": "Login was canceled or failed...",
"NoDevicesFound": "No devices found",
"OutputDir": "Ensuring that the output directory exists",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"CreateNewCertificates": "Creating the signed P12 files...",
"ExtractRootCertificate": "Extracting Root Certificates...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"MovingP12Files": "Copy files to be used in certificate-manager...",
"SettingCertificateManager": "Fixing Tizen certificate manager...",
"SettingsCaCerts": "Setting CA certificates...",
"ChooseRelease": "Choose release...",
"ChooseVersion": "Choose version...",
"ChooseTV": "Choose TV...",
"SelectLanguage": "Select language",
"SelectCertificate": "Select certificate",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "Custom WGT",
"lblSettings": "Settings",
"lblRememberIp": "Remember IPs",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"lblexpires": "Expires",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
@@ -63,34 +42,15 @@
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"FailedRemoveOld": "Failed to remove old app version",
"FailedRemoveOldExtra": "Please remove the old Jellyfin app by hand from the TV",
"CheckingTizenCli": "Checking Tizen CLI...",
"TizenCliFailed": "Tizen CLI installation failed...",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"DownloadingSetupFile": "Downloading installer...",
"InstallingSetupFile": "Installing Tizen Studio CLI...",
"InstallingCertificateManager": "Installing Certificate Manager...",
"ReInstallingCertificateManager": "Installing Certificate Manager add-on failed, want to try again?",
"InstallingCertificateAddOn": "Installing Certificate Manager add-on...",
"CheckingPackageManagerList": "Checking Tizen Package manager",
"PermissionDenied": "Permission Denied",
"AdminPrivRequired": "Administrator privileges are required to create the folder. Please create it manually or rerun the application as Administrator.",
"PathLengthWarning": "Windows Path Length Exceeded",
"PathLengthExceeded": "The default path exceeds Windows Path length. Would you like to change it to C:\\Tizen Studio Cli?",
"lblForceLogin": "Force Samsung Login",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"lblRTL": "Right-to-left (RTL) reading",
"FailedToStopSdbServer": "There is a SDB Server already running, failed to stop...",
"FailedToStopSdbServerTitle": "SDB Server failed to stop!",
"lblJellyfinServer": "Address",
"lblModifyConfig": "Set Jellyfin Config",
"ConfigFailure": "config.json not found in the extracted package.",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"ServerPort": "Port",
"Theme": "Theme",
"SelectTheme": "Select theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
@@ -100,31 +60,13 @@
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblAutoPlayNextEpisode": "Auto play next episode",
"lblRememberAudioSelections": "Remember audio selections",
"lblRememberSubtitleSelections": "Remember subtitle selections",
"lblPlayDefaultAudioTrack": "Play default audio track",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin Config",
"SelectSubtitleMode": "Select Subtitle Mode",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblOpenConfig": "Open config",
"lblValidateLogin": "Validate",
"lblValidateJellyfin": "Validate Jellyfin Server",
"NoJellyfinServer": "Please fill in the Jellyfin server first...",
"JellyfinLoginFailed": "Login failed: Invalid credentials or server error.",
"lblJellyfinServerApi": "API Key",
"UpdateMode": "Update mode",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblUserSettings": "User settings",
"lbluserAutoLogin": "Enable auto login",
"lblJellyfinUser": "Jellyfin user(s)",
"lblFailedUsers": "Failed to retrieve any Jellyfin Users, no Config settings will be changed!",
"lblUpdateMode": "Choose what to update",
"lblJellyfinAPIKey": "Jellyfin API Key",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
@@ -136,9 +78,78 @@
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"minimalCliTitle": "Tizen CLI & Certificate manager required",
"minimalCliMessage": "Tizen CLI & Certificate manager are required to continue.\n\nWe will now download and install Tizen CLI followed by Certificate manager.\nThis may take a few minutes. Please be patient during the installation process.",
"minimalCliStop": "User declined to install Tizen CLI",
"packageAndSign": "Packaging and signing...",
"certRetryFailed": "Retry failed, please create an issue"
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Télécharger et installer",
"InstallationFailed": "Échec de l'installation",
"InstallationSuccessfulOn": "{0} a été installé avec succès !",
"DownloadFailed": "Échec du téléchargement :",
"FailedLoadingReleases": "Échec du chargement des versions :",
"InstallTizenSdb": "Le Tizen SDB est requis mais introuvable. Nouvelle tentative de téléchargement.",
"FailedTizenSdb": "Tizen SDB est requis mais n'a pas pu être trouvé ni téléchargé.",
"ConnectingToDevice": "Connexion à l'appareil...",
"TvNameNotFound": "Le nom du téléviseur est introuvable...",
"TvDuidNotFound": "Le DUID du téléviseur est introuvable...",
"CreatingCertificateProfile": "Création d'un nouveau profil de certificat...",
"InstallationSuccessful": "Installation réussie",
"InstallingPackage": "Installation du paquet sur l'appareil...",
"Output": "Sortie",
"LoadingReleases": "Chargement des versions...",
"ScanningNetwork": "Recherche de téléviseurs Samsung sur le réseau...",
"Ready": "Prêt à l'emploi...",
"NoDevicesFound": "Aucun appareil trouvé",
"GenPassword": "Génération d'un mot de passe aléatoire...",
"GenKeyPair": "Génération de la paire de clés",
"CreateAuthorCsr": "Création du CSR d'auteur...",
"CreateDistributorCSR": "Génération du CSR de distributeur avec DUID...",
"PostAuthorCSR": "Envoi au point de terminaison auteur Samsung...",
"PostDistributorCSR": "Envoi au distributeur Samsung...",
"ExportPfxCertificates": "Exportation des certificats PFX...",
"SelectWGT": "Sélectionner le(s) fichier(s) WGT et/ou TPK",
"lblRelease": "Version",
"lblVersion": "Version",
"lblSelectTv": "Sélectionner le téléviseur",
"lblLanguage": "Langue",
"lblCustomWgt": "Fichier WGT",
"lblSettings": "Paramètres de l'application",
"lblDeletePrevious": "Supprimer l'ancien Jellyfin",
"IpWindowTitle": "Saisir l'adresse IP du téléviseur",
"IpWindowDescription": "Veuillez saisir l'adresse IP de l'appareil :",
"InvalidDeviceIp": "Adresse IP de l'appareil invalide ou appareil introuvable.",
"IpNotListed": "Mon adresse IP n'est pas dans la liste...",
"lblCertifcate": "Certificat",
"lblOther": "Autre",
"DownloadCompleted": "Téléchargement terminé...",
"NoPackageToInstall": "Aucun paquet à installer",
"NoDeviceSelected": "Aucun appareil sélectionné",
"UsingCustomWGT": "Utilisation d'un fichier WGT personnalisé",
"CheckingTizenSdb": "Vérification de Tizen SDB...",
"InitializationFailed": "Échec de l'initialisation...",
"lblForceLogin": "Certificat Samsung obligatoire",
"DeveloperModeRequired": "Le téléviseur Samsung n'est pas en mode développeur...",
"DeveloperIPMismatch": "L'adresse IP du mode développeur Samsung ne correspond pas à l'adresse IP locale de cet appareil. Souhaitez-vous continuer ?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Adresse IP inversée (pour l'arabe/l'hébreu)",
"ServerIP": "IP du serveur",
"Theme": "Thème",
"lblEnableBackdrops": "Activer les arrière-plans",
"lblEnableThemeSongs": "Activer les musiques de thème",
"lblEnableThemeVideos": "Activer les vidéos de thème",
"lblBackdropScreensaver": "Économiseur d'écran avec arrière-plan",
"lblDetailsBanner": "Bannière de détails",
"lblCinemaMode": "Mode cinéma",
"lblNextUpEnabled": "Activer « À suivre »",
"lblEnableExternalVideoPlayers": "Activer les lecteurs vidéo externes",
"lblSkipIntros": "Ignorer les génériques",
"lblSubtitleMode": "Mode sous-titres",
"lblAudioLanguagePreference": "Préférence de langue audio",
"lblSubtitleLanguagePreference": "Préférence de langue des sous-titres",
"lblJellyfinConfig": "Paramètres Jellyfin",
"AudioLanguage": "ex. fr | en | es",
"lblServerSettings": "Paramètres du serveur",
"lblBrowserSettings": "Paramètres du navigateur",
"lblSelectUsers": "Sélectionner le(s) utilisateur(s) à mettre à jour",
"lblValidation": "🍺 Offrez-moi une bière",
"btn_Close": "Fermer",
"lbleasyRight": "C'était facile, non ?",
"NoDevicesFoundRetry": "Aucun appareil trouvé, réessayer avec la carte réseau virtuelle ?",
"RetySearchMsg": "Aucun téléviseur trouvé. Voulez-vous relancer la recherche en incluant les cartes réseau virtuelles ?",
"keyYes": "Oui",
"keyNo": "Non",
"keyContinue": "Continuer",
"keyStop": "Arrêter",
"keyConfirm": "Confirmer",
"packageAndSign": "Empaquetage et signature...",
"alreadyInstalled": "Jellyfin n'a pas pu être installé car il est déjà installé. Veuillez d'abord le supprimer puis réessayer...",
"diagnoseTv": "Diagnostiquer les capacités du téléviseur",
"modiyConfigRequired": "L'application Jellyfin nécessite un nouvel identifiant d'application !",
"deleteExistingVersion": "Suppression d'une version existante...",
"deleteExistingFailed": "La suppression de la version existante a échoué. Veuillez la supprimer manuellement...",
"deleteExistingSuccess": "Version existante supprimée avec succès...",
"deleteExistingNotAllowed": "Suppression impossible. Activez l'option pour autoriser l'outil à supprimer l'application existante...",
"lblLocalIP": "IP locale :",
"lblTryOverwrite": "Écraser la version existante",
"lblUseServerScripts": "Télécharger les plugins Jellyfin",
"lblEnableDevLogs": "Activer le débogage TV",
"lblOpenDebugWindow": "Ouvrir le journal TV",
"lblStartLog": "Commencer",
"lblStopLog": "Arrêter",
"lblSaveLogs": "Sauvegarder",
"lblLaunchOnInstall": "Ouvrir après l'installation",
"insufficientSpace": "Votre appareil ne dispose pas d'espace suffisant, veuillez supprimer certaines applications et réessayer...",
"lblKeepWGTFile": "Conserver le fichier WGT",
"AuthorMismatch": "Incompatibilité du certificat d'auteur, veuillez signer à nouveau le package avec le certificat correct.",
"FixYouTube153": "Correction du plugin YouTube (Erreur 153)",
"lblBasePath": "Chemin de base",
"lblJellyfinUsername": "Nom d'utilisateur",
"lblJellyfinPassword": "Mot de passe",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Paramètres de connexion automatique",
"lblBasePathHint": "Astuce: Collez l'URL complète (ex: https://host.com/chemin/jellyfin) pour remplir automatiquement",
"lblTestServer": "Tester serveur",
"lblLogout": "Déconnexion",
"lblEnableAutoLoginConfig": "Activer le patch config pour auto-login",
"lblTabServer": "Serveur",
"lblTabPlayback": "Lecture",
"lblServerInputMode": "Mode de saisie",
"lblServerUrl": "URL du serveur",
"lblConnectionStatus": "Statut serveur",
"lblAdvancedSettings": "Paramètres avancés",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "Style CSS",
"lblCssSettings": "Paramètres CSS personnalisés",
"lblCustomCss": "Code CSS personnalisé",
"lblCssHint": "Entrez du CSS ou utilisez @import pour charger des thèmes externes",
"lblValidateCss": "Valider CSS",
"lblCssValidationStatus": "Statut de validation",
"lblCssEmpty": "Aucun CSS à valider",
"lblClearCss": "Effacer",
"lblCssValidating": "Validation en cours...",
"lblCssUrlFailed": "{0} URL(s) inaccessible(s)",
"lblCssUrlsValid": "{0} URL(s) validée(s) avec succès",
"lblCssSyntaxValid": "Syntaxe CSS valide",
"lblCssUnmatchedBrace": "Accolades non appariées { }",
"lblCssUnmatchedParen": "Parenthèses non appariées ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Cliquez sur un thème pour l'insérer et le valider automatiquement. Visitez le dépôt GitHub pour les aperçus.",
"lblTabMainSettings": "Principal",
"lblMainSettings": "Paramètres de l'application",
"lblRefreshUsers": "Actualiser utilisateurs",
"lblUserSelectionHint": "Les utilisateurs sélectionnés seront configurés lors de l'installation",
"subnetMismatch": "Les appareils se trouvent dans différents sous-réseaux (réseau).",
"UpdateAvailable": "Mise à jour disponible",
"UpdateCurrentVersion": "Version actuelle:",
"UpdateLatestVersion": "Dernière version:",
"UpdateReleaseNotes": "Notes de version:",
"UpdateManual": "Ouvrir les versions",
"UpdateAutomatic": "Mettre à jour",
"UpdateSkip": "Ignorer cette version",
"UpdateDownloading": "Téléchargement...",
"UpdateApplying": "Application de la mise à jour",
"UpdateApplyingMessage": "L'application va redémarrer pour terminer la mise à jour.",
"UpdateError": "Échec de la mise à jour",
"UpdateCheckFailed": "Échec de la vérification des mises à jour",
"IncompatiblePackage": "Version du package non compatible avec l'appareil.",
"IncompatiblePackageDetailed": "La version du package {0} n'est pas compatible avec Tizen OS {1}",
"lblMdnsWarning": "Attention : Vous utilisez un nom d'hôte mDNS (.local). Les TV Samsung ne peuvent pas résoudre de manière fiable les adresses .local, ce qui peut faire apparaître le serveur comme \"undefined\" sur votre TV — surtout après des interruptions réseau. Utilisez plutôt une adresse IP directe (ex. 192.168.1.100) pour une connexion stable."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -1,58 +1,37 @@
{
"DownloadAndInstall": "Downloaden & Installeren",
"InstallationFailed": "Installatie mislukt",
"InstallationSuccessfulOn": "Jellyfin is succesvol geïnstalleerd!",
"InstallationSuccessfulOn": "{0} is succesvol geïnstalleerd!",
"DownloadFailed": "Download mislukt:",
"FailedLoadingReleases": "Laden van releases mislukt:",
"PleaseInstallTizen": "Tizen CLI is vereist maar niet gevonden. Installeer eerst Tizen Studio.",
"DownloadingPackage": "Pakket downloaden...",
"InstallTizenSdb": "Tizen SDB is vereist maar niet gevonden. opnieuw proberen te downloaden.",
"FailedTizenSdb": "Tizen SDB is vereist maar kon niet gevonden en gedownload worden.",
"ConnectingToDevice": "Verbinden met apparaat...",
"RetrievingDeviceAddress": "Apparaatadres ophalen...",
"TvNameNotFound": "TV-naam kon niet worden gevonden...",
"TvDuidNotFound": "TV-duid kon niet worden gevonden...",
"UpdatingCertificateProfile": "Certificaatsprofiel bijwerken...",
"CreatingCertificateProfile": "Nieuw certificaatprofiel maken...",
"PackagingWgtWithCertificate": "Wgt-bestand verpakken met certificaat...",
"InstallationSuccessful": "Installatie succesvol",
"InstallingPackage": "Pakket installeren op apparaat...",
"InstallationMaybeFailed": "Installatie is mogelijk mislukt",
"Output": "Uitvoer",
"LoadingReleases": "Releases laden...",
"ScanningNetwork": "Netwerk scannen voor Samsung TV...",
"CheckTizenOS": "Tizen OS-versie controleren...",
"SuccessAuthCode": "Succesvol autorisatiecode verkregen",
"Ready": "Klaar voor gebruik...",
"SamsungLogin": "Aanmelden bij Samsung...",
"FailedAuthCode": "Login is geannuleerd of mislukt...",
"NoDevicesFound": "Geen apparaten gevonden",
"OutputDir": "Zorgen dat de uitvoermap bestaat",
"GenPassword": "Willekeurig wachtwoord genereren...",
"GenKeyPair": "Sleutelpaar genereren",
"CreateAuthorCsr": "Author CSR aanmaken...",
"CreateDistributorCSR": "Distributor CSR genereren met DUID...",
"PostAuthorCSR": "Posten naar Samsung author endpoint...",
"PostDistributorCSR": "Posten naar Samsung distributor...",
"CreateNewCertificates": "Ondertekende P12-bestanden aanmaken...",
"ExtractRootCertificate": "Rootcertificaten extraheren...",
"ExportPfxCertificates": "PFX-certificaten exporteren...",
"MovingP12Files": "Bestanden kopiëren voor gebruik in certificate-manager...",
"SettingCertificateManager": "Tizen certificate manager repareren...",
"SettingsCaCerts": "CA-certificaten instellen...",
"ChooseRelease": "Release kiezen...",
"ChooseVersion": "Versie kiezen...",
"ChooseTV": "TV kiezen...",
"SelectLanguage": "Taal selecteren",
"SelectCertificate": "Certificaat selecteren",
"SelectWGT": "WGT- en/of TPK-bestand(en) selecteren",
"lblRelease": "Release",
"lblVersion": "Versie",
"lblSelectTv": "Kies TV",
"lblLanguage": "Taal",
"lblCustomWgt": "Aangepaste WGT",
"lblSettings": "Instellingen",
"lblRememberIp": "IP-adressen onthouden",
"lblCustomWgt": "WGT Bestand",
"lblSettings": "Applicatie-instellingen",
"lblDeletePrevious": "Oude Jellyfin verwijderen",
"lblexpires": "Verloopt",
"IpWindowTitle": "TV IP invoeren",
"IpWindowDescription": "Voer het IP-adres van het apparaat in:",
"InvalidDeviceIp": "Ongeldig apparaat-IP of apparaat niet gevonden.",
@@ -63,34 +42,15 @@
"NoPackageToInstall": "Geen pakket om te installeren",
"NoDeviceSelected": "Geen apparaat geselecteerd",
"UsingCustomWGT": "Aangepast WGT-bestand gebruiken",
"FailedRemoveOld": "Verwijderen oude app-versie mislukt",
"FailedRemoveOldExtra": "Verwijder de oude Jellyfin-app handmatig van de TV",
"CheckingTizenCli": "Tizen CLI controleren...",
"TizenCliFailed": "Tizen CLI-installatie mislukt...",
"CheckingTizenSdb": "Tizen SDB controleren...",
"InitializationFailed": "Initialisatie mislukt...",
"DownloadingSetupFile": "Installer downloaden...",
"InstallingSetupFile": "Tizen Studio CLI installeren...",
"InstallingCertificateManager": "Certificate Manager installeren...",
"ReInstallingCertificateManager": "Het installeren van de Certificate Manager-invoegtoepassing is mislukt. Wilt u het opnieuw proberen?",
"InstallingCertificateAddOn": "Certificate Manager add-on installeren...",
"CheckingPackageManagerList": "Tizen Package manager controleren",
"PermissionDenied": "Toestemming geweigerd",
"AdminPrivRequired": "Beheerdersrechten zijn vereist om de map aan te maken. Maak deze handmatig aan of voer de applicatie uit als Beheerder.",
"PathLengthWarning": "Windows-padlengte overschreden",
"PathLengthExceeded": "Het standaardpad overschrijdt de Windows-padlengte. Wilt u dit wijzigen naar C:\\Tizen Studio Cli?",
"lblForceLogin": "Samsung Login forceren",
"lblForceLogin": "Forceer Samsung-certificaat",
"DeveloperModeRequired": "Samsung TV staat niet in ontwikkelaarsmodus...",
"DeveloperIPMismatch": "Samsung ontwikkelaarsmodus IP komt niet overeen met het lokale IP van dit apparaat, wilt u doorgaan?",
"lblRTL": "Rechts-naar-links (RTL) lezen",
"FailedToStopSdbServer": "Er is al een SDB Server actief, stoppen mislukt...",
"FailedToStopSdbServerTitle": "SDB Server kon niet stoppen!",
"lblJellyfinServer": "Adres",
"lblModifyConfig": "Jellyfin Config instellen",
"ConfigFailure": "config.json niet gevonden in het uitgepakte pakket.",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Omgekeerd IP-adres (voor Arabisch/Hebreeuws)",
"ServerIP": "Server IP",
"ServerPort": "Poort",
"Theme": "Thema",
"SelectTheme": "Thema selecteren",
"lblEnableBackdrops": "Achtergronden inschakelen",
"lblEnableThemeSongs": "Themaliedjes inschakelen",
"lblEnableThemeVideos": "Themavideo's inschakelen",
@@ -100,31 +60,13 @@
"lblNextUpEnabled": "Volgende ingeschakeld",
"lblEnableExternalVideoPlayers": "Externe videospelers inschakelen",
"lblSkipIntros": "Intro's overslaan",
"lblAutoPlayNextEpisode": "Automatisch volgende aflevering afspelen",
"lblRememberAudioSelections": "Audioselecties onthouden",
"lblRememberSubtitleSelections": "Ondertitelingselecties onthouden",
"lblPlayDefaultAudioTrack": "Standaard audiospoor afspelen",
"lblSubtitleMode": "Ondertitelingmodus",
"lblAudioLanguagePreference": "Voorkeur audiotaal",
"lblSubtitleLanguagePreference": "Voorkeur ondertitelingtaal",
"lblJellyfinConfig": "Jellyfin Config",
"SelectSubtitleMode": "Ondertitelingmodus selecteren",
"lblJellyfinConfig": "Jellyfin-instellingen",
"AudioLanguage": "bijv. nl | en | de",
"lblOpenConfig": "Config openen",
"lblValidateLogin": "Valideren",
"lblValidateJellyfin": "Jellyfin Server valideren",
"NoJellyfinServer": "Vul eerst de Jellyfin server in...",
"JellyfinLoginFailed": "Login mislukt: Ongeldige inloggegevens of serverfout.",
"lblJellyfinServerApi": "API-sleutel",
"UpdateMode": "Updatemodus",
"lblServerSettings": "Serverinstellingen",
"lblBrowserSettings": "Browserinstellingen",
"lblUserSettings": "Gebruikersinstellingen",
"lbluserAutoLogin": "Automatisch inloggen inschakelen",
"lblJellyfinUser": "Jellyfin gebruiker(s)",
"lblFailedUsers": "Kon geen Jellyfin-gebruikers ophalen, geen Config-instellingen zullen worden gewijzigd!",
"lblUpdateMode": "Kies wat moet worden bijgewerkt",
"lblJellyfinAPIKey": "Jellyfin API-sleutel",
"lblSelectUsers": "Selecteer gebruiker(s) om bij te werken",
"lblValidation": "🍺 Trakteer me op een biertje",
"btn_Close": "Sluiten",
@@ -136,9 +78,78 @@
"keyContinue": "Doorgaan",
"keyStop": "Stop",
"keyConfirm": "Bevestig",
"minimalCliTitle": "Tizen CLI & Certificaatbeheerder vereist",
"minimalCliMessage": "Tizen CLI en Certificaatbeheer zijn vereist om door te gaan.\n\nWe gaan nu Tizen CLI downloaden en installeren, gevolgd door Certificaatbeheer.\nDit kan enkele minuten duren. Wees geduldig tijdens het installatieproces.",
"minimalCliStop": "Gebruiker weigerde Tizen CLI te installeren",
"packageAndSign": "Inpakken en ondertekenen...",
"certRetryFailed": "Opnieuw proberen mislukt, maak een probleem aan"
"alreadyInstalled": "Jellyfin kon niet worden geïnstalleerd omdat het al is geïnstalleerd. Verwijder het eerst en probeer het opnieuw.",
"diagnoseTv": "Diagnose van tv-mogelijkheden",
"modiyConfigRequired": "De Jellyfin-app heeft een nieuwe app-ID nodig!",
"deleteExistingVersion": "Bestaande versie verwijderen...",
"deleteExistingFailed": "Het verwijderen van de bestaande versie is mislukt. Verwijder de versie handmatig....",
"deleteExistingSuccess": "Bestaande versie succesvol verwijderd...",
"deleteExistingNotAllowed": "Verwijderen niet toegestaan. Schakel de instelling in zodat de tool de bestaande app kan verwijderen...",
"lblLocalIP": "Lokaal IP-adres:",
"lblTryOverwrite": "Bestaande versie overschrijven",
"lblUseServerScripts": "Jellyfin plugins downloaden",
"lblEnableDevLogs": "TV-foutopsporing inschakelen",
"lblOpenDebugWindow": "Open TV-logboek",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Opslaan",
"lblLaunchOnInstall": "Openen na installeren",
"insufficientSpace": "Er is onvoldoende ruimte op uw apparaat. Verwijder enkele apps en probeer het opnieuw...",
"lblKeepWGTFile": "WGT-bestand behouden",
"AuthorMismatch": "Auteurscertificaat komt niet overeen. Onderteken het pakket opnieuw met het juiste certificaat.",
"FixYouTube153": "YouTube-plug-in repareren (fout 153)",
"lblBasePath": "Basispad",
"lblJellyfinUsername": "Gebruikersnaam",
"lblJellyfinPassword": "Wachtwoord",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-login instellingen",
"lblBasePathHint": "Tip: Plak volledige URL (bijv. https://host.com/pad/jellyfin) om alle velden automatisch in te vullen",
"lblTestServer": "Server testen",
"lblLogout": "Uitloggen",
"lblEnableAutoLoginConfig": "Config-patching voor auto-login inschakelen",
"lblTabServer": "Server",
"lblTabPlayback": "Afspelen",
"lblServerInputMode": "Invoermodus",
"lblServerUrl": "Server-URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Geavanceerde instellingen",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Stijl",
"lblCssSettings": "Aangepaste CSS-instellingen",
"lblCustomCss": "Aangepaste CSS-code",
"lblCssHint": "Voer CSS in of gebruik @import voor externe thema's",
"lblValidateCss": "CSS valideren",
"lblCssValidationStatus": "Validatiestatus",
"lblCssEmpty": "Geen CSS om te valideren",
"lblClearCss": "Wissen",
"lblCssValidating": "Valideren...",
"lblCssUrlFailed": "{0} URL('s) onbereikbaar",
"lblCssUrlsValid": "{0} URL('s) succesvol gevalideerd",
"lblCssSyntaxValid": "CSS-syntaxis geldig",
"lblCssUnmatchedBrace": "Ongelijke accolades { }",
"lblCssUnmatchedParen": "Ongelijke haakjes ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Klik op een thema om het in te voegen en automatisch te valideren. Bezoek de GitHub-repo voor previews.",
"lblTabMainSettings": "Hoofd",
"lblMainSettings": "Toepassingsinstellingen",
"lblRefreshUsers": "Gebruikers vernieuwen",
"lblUserSelectionHint": "Geselecteerde gebruikers worden geconfigureerd tijdens de installatie",
"subnetMismatch": "De apparaten bevinden zich in verschillende subnetten (netwerken).",
"UpdateAvailable": "Update beschikbaar",
"UpdateCurrentVersion": "Huidige versie:",
"UpdateLatestVersion": "Nieuwste versie:",
"UpdateReleaseNotes": "Release-opmerkingen:",
"UpdateManual": "Releases openen",
"UpdateAutomatic": "Nu bijwerken",
"UpdateSkip": "Deze versie overslaan",
"UpdateDownloading": "Update downloaden...",
"UpdateApplying": "Update toepassen",
"UpdateApplyingMessage": "De applicatie wordt opnieuw gestart om de update te voltooien.",
"UpdateError": "Update mislukt",
"UpdateCheckFailed": "Controle op updates mislukt",
"IncompatiblePackage": "Pakketversie niet compatibel met apparaat.",
"IncompatiblePackageDetailed": "Pakketversie {0} niet compatibel met Tizen OS {1}",
"lblMdnsWarning": "Waarschuwing: Je gebruikt een mDNS-hostnaam (.local). Samsung TV's kunnen .local-adressen niet betrouwbaar omzetten, waardoor de server als \"undefined\" op je TV kan verschijnen — vooral na netwerkonderbrekingen. Gebruik in plaats daarvan een direct IP-adres (bijv. 192.168.1.100) voor een stabiele verbinding."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Aviso: Você está usando um nome de host mDNS (.local). TVs Samsung não conseguem resolver endereços .local de forma confiável, o que pode fazer o servidor aparecer como \"undefined\" na sua TV — especialmente após interrupções de rede. Use um endereço IP direto (ex. 192.168.1.100) para uma conexão estável."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "İndir ve Kur",
"InstallationFailed": "Kurulum başarısız",
"InstallationSuccessfulOn": "{0} başarıyla kuruldu!",
"DownloadFailed": "İndirme başarısız:",
"FailedLoadingReleases": "Sürümler yüklenemedi:",
"InstallTizenSdb": "Tizen SDB gerekli ancak bulunamadı. İndirme yeniden deneniyor.",
"FailedTizenSdb": "Tizen SDB gerekli ancak bulunamadı ve indirilemedi.",
"ConnectingToDevice": "Cihaza bağlanılıyor...",
"TvNameNotFound": "TV Adı bulunamadı...",
"TvDuidNotFound": "TV duid bulunamadı...",
"CreatingCertificateProfile": "Yeni sertifika profili oluşturuluyor...",
"InstallationSuccessful": "Kurulum başarılı",
"InstallingPackage": "Paket cihaza kuruluyor...",
"Output": ıktı",
"LoadingReleases": "Sürümler yükleniliyor...",
"ScanningNetwork": "Samsung TV için ağ taranıyor...",
"Ready": "Kullanıma hazır...",
"NoDevicesFound": "Cihaz bulunamadı",
"GenPassword": "Rastgele şifre oluşturuluyor...",
"GenKeyPair": "Anahtar çifti oluşturuluyor",
"CreateAuthorCsr": "Yazar CSR oluşturuluyor...",
"CreateDistributorCSR": "DUID ile Dağıtıcı CSR oluşturuluyor...",
"PostAuthorCSR": "Samsung yazar uç noktasına gönderiliyor...",
"PostDistributorCSR": "Samsung dağıtıcısına gönderiliyor...",
"ExportPfxCertificates": "PFX sertifikaları dışa aktarılıyor...",
"SelectWGT": "WGT ve/veya TPK dosyası seç",
"lblRelease": "Sürüm",
"lblVersion": "Versiyon",
"lblSelectTv": "TV Seç",
"lblLanguage": "Dil",
"lblCustomWgt": "WGT Dosyası",
"lblSettings": "Uygulama ayarları",
"lblDeletePrevious": "Eski Jellyfin'i kaldır",
"IpWindowTitle": "TV IP Adresini Girin",
"IpWindowDescription": "Lütfen cihazın IP adresini girin:",
"InvalidDeviceIp": "Geçersiz cihaz IP'si veya cihaz bulunamadı.",
"IpNotListed": "Benim IP'm listede yok...",
"lblCertifcate": "Sertifika",
"lblOther": "Diğer",
"DownloadCompleted": "İndirme tamamlandı...",
"NoPackageToInstall": "Kurulacak paket yok",
"NoDeviceSelected": "Cihaz seçilmedi",
"UsingCustomWGT": "Özel WGT dosyası kullanılıyor",
"CheckingTizenSdb": "Tizen SDB kontrol ediliyor...",
"InitializationFailed": "Başlatma başarısız...",
"lblForceLogin": "Samsung sertifikasını zorla",
"DeveloperModeRequired": "Samsung TV geliştirici modunda değil...",
"DeveloperIPMismatch": "Samsung Geliştirici modu IP'si bu cihazın yerel IP'siyle eşleşmiyor, devam etmek istiyor musunuz?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Ters IP (Arapça/İbranice için)",
"ServerIP": "Sunucu IP",
"Theme": "Tema",
"lblEnableBackdrops": "Arka planları etkinleştir",
"lblEnableThemeSongs": "Tema şarkılarını etkinleştir",
"lblEnableThemeVideos": "Tema videolarını etkinleştir",
"lblBackdropScreensaver": "Arka plan ekran koruyucu",
"lblDetailsBanner": "Detay bannerı",
"lblCinemaMode": "Sinema modu",
"lblNextUpEnabled": "Sıradaki etkin",
"lblEnableExternalVideoPlayers": "Harici video oynatıcıları etkinleştir",
"lblSkipIntros": "Intro'ları atla",
"lblSubtitleMode": "Alt yazı modu",
"lblAudioLanguagePreference": "Ses dili tercihi",
"lblSubtitleLanguagePreference": "Alt yazı dili tercihi",
"lblJellyfinConfig": "Jellyfin ayarları",
"AudioLanguage": "örn. tr | en | da",
"lblServerSettings": "Sunucu ayarları",
"lblBrowserSettings": "Tarayıcı ayarları",
"lblSelectUsers": "Güncellenecek kullanıcı(lar)ı seç",
"lblValidation": "🍺 Bana bir bira ısmarla",
"btn_Close": "Kapat",
"lbleasyRight": "Kolaydı, değil mi?",
"NoDevicesFoundRetry": "Cihaz bulunamadı, sanal NIC ile yeniden denensin mi?",
"RetySearchMsg": "TV bulunamadı, arama sanal ağ kartları dahil edilerek tekrar yapılsın mı?",
"keyYes": "Evet",
"keyNo": "Hayır",
"keyContinue": "Devam",
"keyStop": "Durdur",
"keyConfirm": "Onayla",
"packageAndSign": "Paketleniyor ve imzalanıyor...",
"alreadyInstalled": "Jellyfin zaten kurulu olduğu için kurulamadı, lütfen önce kaldırın ve tekrar deneyin...",
"diagnoseTv": "TV yeteneklerini tani",
"modiyConfigRequired": "Jellyfin uygulamasının yeni bir uygulama kimliğine ihtiyacı var!",
"deleteExistingVersion": "Mevcut sürüm siliniyor...",
"deleteExistingFailed": "Mevcut sürümü silme başarısız. Lütfen manuel olarak silin...",
"deleteExistingSuccess": "Mevcut sürüm başarıyla silindi...",
"deleteExistingNotAllowed": "Silmeye izin verilmiyor. Aracın mevcut uygulamayı kaldırmasına izin vermek için ayarı etkinleştirin...",
"lblLocalIP": "Yerel IP:",
"lblTryOverwrite": "Mevcut sürümü üstüne yaz",
"lblUseServerScripts": "Jellyfin eklentilerini indir",
"lblEnableDevLogs": "TV Hata Ayıklamasını Etkinleştir",
"lblOpenDebugWindow": "TV Logunu Aç",
"lblStartLog": "Başlat",
"lblStopLog": "Durdur",
"lblSaveLogs": "Kaydet",
"lblLaunchOnInstall": "Kurulumdan sonra aç",
"insufficientSpace": "Cihazınızda yetersiz alan var, lütfen bazı uygulamaları kaldırın ve tekrar deneyin...",
"lblKeepWGTFile": "WGT dosyasını koru",
"AuthorMismatch": "Yazar Sertifikası uyumsuzluğu, lütfen paketi doğru sertifika ile yeniden imzalayın.",
"FixYouTube153": "YouTube Eklentisini Düzelt (Hata 153)",
"lblBasePath": "Temel Yol",
"lblJellyfinUsername": "Kullanıcı Adı",
"lblJellyfinPassword": "Şifre",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Otomatik Giriş Ayarları",
"lblBasePathHint": "İpucu: Tüm alanları otomatik doldurmak için tam URL yapıştırın",
"lblTestServer": "Sunucuyu Test Et",
"lblLogout": ıkış Yap",
"lblEnableAutoLoginConfig": "Otomatik giriş için config yaması etkinleştir",
"lblTabServer": "Sunucu",
"lblTabPlayback": "Oynatma",
"lblServerInputMode": "Giriş Modu",
"lblServerUrl": "Sunucu URL",
"lblConnectionStatus": "Sunucu Durumu",
"lblAdvancedSettings": "Gelişmiş Ayarlar",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Stili",
"lblCssSettings": "Özel CSS Ayarları",
"lblCustomCss": "Özel CSS Kodu",
"lblCssHint": "CSS girin veya harici temalar için @import kullanın",
"lblValidateCss": "CSS Doğrula",
"lblCssValidationStatus": "Doğrulama Durumu",
"lblCssEmpty": "Doğrulanacak CSS yok",
"lblClearCss": "Temizle",
"lblCssValidating": "Doğrulanıyor...",
"lblCssUrlFailed": "{0} URL(ler) ulaşılamıyor",
"lblCssUrlsValid": "{0} URL(ler) başarıyla doğrulandı",
"lblCssSyntaxValid": "CSS söz dizimi geçerli",
"lblCssUnmatchedBrace": "Eşleşmeyen süslü parantezler { }",
"lblCssUnmatchedParen": "Eşleşmeyen parantezler ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Bir temaya tıklayarak ekleyin ve otomatik olarak doğrulayın. Önizlemeler için GitHub deposunu ziyaret edin.",
"lblTabMainSettings": "Ana",
"lblMainSettings": "Uygulama Ayarları",
"lblRefreshUsers": "Kullanıcıları Yenile",
"lblUserSelectionHint": "Seçilen kullanıcılar kurulum sırasında yapılandırılacak",
"subnetMismatch": "Cihazlar farklı alt ağlarda (ağda) bulunmaktadır.",
"UpdateAvailable": "Güncelleme mevcut",
"UpdateCurrentVersion": "Mevcut sürüm:",
"UpdateLatestVersion": "Son sürüm:",
"UpdateReleaseNotes": "Sürüm notları:",
"UpdateManual": "Sürümleri aç",
"UpdateAutomatic": "Şimdi güncelle",
"UpdateSkip": "Bu sürümü atla",
"UpdateDownloading": "Güncelleme indiriliyor...",
"UpdateApplying": "Güncelleme uygulanıyor",
"UpdateApplyingMessage": "Güncellemeyi tamamlamak için uygulama yeniden başlatılacak.",
"UpdateError": "Güncelleme başarısız",
"UpdateCheckFailed": "Güncelleme kontrolü başarısız",
"IncompatiblePackage": "Paket sürümü cihazla uyumlu değil.",
"IncompatiblePackageDetailed": "Paket sürümü {0}, Tizen OS {1} ile uyumlu değil",
"lblMdnsWarning": "Uyarı: Bir mDNS ana bilgisayar adı (.local) kullanıyorsunuz. Samsung TV'ler .local adreslerini güvenilir şekilde çözümleyemez, bu da sunucunun TV'nizde \"undefined\" olarak görünmesine neden olabilir — özellikle ağ kesintilerinden sonra. Kararlı bir bağlantı için bunun yerine doğrudan bir IP adresi (ör. 192.168.1.100) kullanın."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -0,0 +1,155 @@
{
"DownloadAndInstall": "Download & Install",
"InstallationFailed": "Installation failed",
"InstallationSuccessfulOn": "{0} has been successfully installed!",
"DownloadFailed": "Download failed:",
"FailedLoadingReleases": "Failed to load releases:",
"InstallTizenSdb": "The Tizen SDB is required but not found. Retrying to download.",
"FailedTizenSdb": "Tizen SDB is required but could not be found and downloaded.",
"ConnectingToDevice": "Connecting to device...",
"TvNameNotFound": "TV Name could not be found...",
"TvDuidNotFound": "TV duid could not be found...",
"CreatingCertificateProfile": "Creating new certificate profile...",
"InstallationSuccessful": "Installation successful",
"InstallingPackage": "Installing package on device...",
"Output": "Output",
"LoadingReleases": "Loading releases...",
"ScanningNetwork": "Scanning network for Samsung TV...",
"Ready": "Ready for use...",
"NoDevicesFound": "No devices found",
"GenPassword": "Generating random password...",
"GenKeyPair": "Generating keypair",
"CreateAuthorCsr": "Creating Author CSR...",
"CreateDistributorCSR": "Generating Distributor CSR with DUID...",
"PostAuthorCSR": "Posting to Samsung author endpoint...",
"PostDistributorCSR": "Posting to Samsung distributor...",
"ExportPfxCertificates": "Exporting PFX certificates...",
"SelectWGT": "Select WGT and/or TPK file(s)",
"lblRelease": "Release",
"lblVersion": "Version",
"lblSelectTv": "Select TV",
"lblLanguage": "Language",
"lblCustomWgt": "WGT File",
"lblSettings": "Application settings",
"lblDeletePrevious": "Remove old Jellyfin",
"IpWindowTitle": "Enter TV IP",
"IpWindowDescription": "Please enter the device's IP address:",
"InvalidDeviceIp": "Invalid device IP or device not found.",
"IpNotListed": "My IP is not listed...",
"lblCertifcate": "Certificate",
"lblOther": "Other",
"DownloadCompleted": "Download completed...",
"NoPackageToInstall": "No package to install",
"NoDeviceSelected": "No device selected",
"UsingCustomWGT": "Using custom WGT file",
"CheckingTizenSdb": "Checking Tizen SDB...",
"InitializationFailed": "Initialization failed...",
"lblForceLogin": "Force Samsung certificate",
"DeveloperModeRequired": "Samsung TV is not in developer mode...",
"DeveloperIPMismatch": "Samsung Developer mode IP doesn't match this devices local IP, do you wish to continue?",
"DeveloperIPReversed": "IP is in reversed order on the TV, please re-enable developer mode and type your local IP in reversed order. (ex: 192.168.1.2 → 2.1.168.192)",
"lblRTL": "Reverse IP (for Arabic/Hebrew)",
"ServerIP": "Server IP",
"Theme": "Theme",
"lblEnableBackdrops": "Enable backdrops",
"lblEnableThemeSongs": "Enable theme songs",
"lblEnableThemeVideos": "Enable theme videos",
"lblBackdropScreensaver": "Backdrop screensaver",
"lblDetailsBanner": "Details banner",
"lblCinemaMode": "Cinema mode",
"lblNextUpEnabled": "Next up enabled",
"lblEnableExternalVideoPlayers": "Enable external video players",
"lblSkipIntros": "Skip intros",
"lblSubtitleMode": "Subtitle mode",
"lblAudioLanguagePreference": "Audio language preference",
"lblSubtitleLanguagePreference": "Subtitle language preference",
"lblJellyfinConfig": "Jellyfin settings",
"AudioLanguage": "e.g. en | da | nl",
"lblServerSettings": "Server settings",
"lblBrowserSettings": "Browser settings",
"lblSelectUsers": "Select user(s) to update",
"lblValidation": "🍺 Buy me a beer",
"btn_Close": "Close",
"lbleasyRight": "That was easy, right?",
"NoDevicesFoundRetry": "No devices found, retry with virtual NIC?",
"RetySearchMsg": "No TV's found, want to search again with virtual network cards included in the search?",
"keyYes": "Yes",
"keyNo": "No",
"keyContinue": "Continue",
"keyStop": "Stop",
"keyConfirm": "Confirm",
"packageAndSign": "Packaging and signing...",
"alreadyInstalled": "Jellyfin couldn't not be installed because its already installed, please remove first and try again...",
"diagnoseTv": "Diagnose TV capabilities",
"modiyConfigRequired": "Jellyfin app needs a new app id!",
"deleteExistingVersion": "Deleting an existing version...",
"deleteExistingFailed": "Deleting the existing version failed. Please delete it manually...",
"deleteExistingSuccess": "Existing version successfully deleted...",
"deleteExistingNotAllowed": "Deletion not allowed. Enable the setting to allow the tool to remove the existing app...",
"lblLocalIP": "Local IP:",
"lblTryOverwrite": "Overwrite existing version",
"lblUseServerScripts": "Download Jellyfin plugins",
"lblEnableDevLogs": "Enable TV Debug",
"lblOpenDebugWindow": "Open TV Log",
"lblStartLog": "Start",
"lblStopLog": "Stop",
"lblSaveLogs": "Save",
"lblLaunchOnInstall": "Open after installation",
"insufficientSpace": "Your device has insufficient space, please remove some apps and try again...",
"lblKeepWGTFile": "Preserve WGT file",
"AuthorMismatch": "Author Certificate mismatch, please re-sign the package with correct certificate.",
"FixYouTube153": "Fix YouTube Plugin (Error 153)",
"lblBasePath": "Base Path",
"lblJellyfinUsername": "Username",
"lblJellyfinPassword": "Password",
"lblAuthenticate": "Test Login",
"lblAutoLoginSettings": "Auto-Login Settings",
"lblBasePathHint": "Tip: Paste full URL (e.g., https://host.com/path/jellyfin) to auto-fill all fields",
"lblTestServer": "Test Server",
"lblLogout": "Logout",
"lblEnableAutoLoginConfig": "Enable config patching for auto-login",
"lblTabServer": "Server",
"lblTabPlayback": "Playback",
"lblServerInputMode": "Input Mode",
"lblServerUrl": "Server URL",
"lblConnectionStatus": "Server Status",
"lblAdvancedSettings": "Advanced Settings",
"lblGitHubToken": "GitHub Token (PAT)",
"lblGitHubTokenHint": "Optional. Prevents API rate limiting when fetching releases. Create one at GitHub > Settings > Developer settings > Personal access tokens.",
"lblTabCss": "CSS Style",
"lblCssSettings": "Custom CSS Settings",
"lblCustomCss": "Custom CSS Code",
"lblCssHint": "Enter custom CSS or use @import to load external themes",
"lblValidateCss": "Validate CSS",
"lblCssValidationStatus": "Validation Status",
"lblCssEmpty": "No CSS to validate",
"lblClearCss": "Clear",
"lblCssValidating": "Validating...",
"lblCssUrlFailed": "{0} URL(s) unreachable",
"lblCssUrlsValid": "{0} URL(s) validated successfully",
"lblCssSyntaxValid": "CSS syntax valid",
"lblCssUnmatchedBrace": "Unmatched braces { }",
"lblCssUnmatchedParen": "Unmatched parentheses ( )",
"lblJellyThemes": "JellyThemes",
"lblJellyThemesHint": "Click a theme to insert it and auto-validate. Visit the GitHub repo for previews.",
"lblTabMainSettings": "Main",
"lblMainSettings": "Application Settings",
"lblRefreshUsers": "Refresh Users",
"lblUserSelectionHint": "Selected users will be configured during installation",
"subnetMismatch": "Devices are in different subnets (network)",
"UpdateAvailable": "Update Available",
"UpdateCurrentVersion": "Current version:",
"UpdateLatestVersion": "Latest version:",
"UpdateReleaseNotes": "Release notes:",
"UpdateManual": "Open Releases",
"UpdateAutomatic": "Update Now",
"UpdateSkip": "Skip this version",
"UpdateDownloading": "Downloading update...",
"UpdateApplying": "Applying Update",
"UpdateApplyingMessage": "The application will restart to complete the update.",
"UpdateError": "Update failed",
"UpdateCheckFailed": "Failed to check for updates",
"IncompatiblePackage": "Package version not compatible with device.",
"IncompatiblePackageDetailed": "Package version {0} not compatible with Tizen OS {1}",
"lblMdnsWarning": "Warning: You are using an mDNS hostname (.local). Samsung TVs cannot reliably resolve .local addresses, which may cause the server to appear as \"undefined\" on your TV — especially after network interruptions. Use a direct IP address (e.g. 192.168.1.100) instead for a stable connection."
}

View File

@@ -1,3 +0,0 @@
Do not delete me!
This folder will be used to place the root certificates comming from the .jar files!

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<profiles active="dev" version="3.1">
<profile name="dev">
<profileitem ca="" distributor="0" key="/home/developer/author.p12" password="eF0GFnArm/35qusNw7gjmQ==" rootca=""/>
<profileitem ca="" distributor="1" key="/home/developer/tizen-studio/tools/certificate-generator/certificates/distributor/tizen-distributor-signer.p12" password="Vy63flx5JBMc5GA4iEf8oFy+8aKE7FX/+arrDcO4I5k=" rootca=""/>
<profileitem ca="" distributor="2" key="" password="xmEcrXPl1ss=" rootca=""/>
</profile>
</profiles>

View File

@@ -0,0 +1 @@
placeholder

View File

@@ -1,6 +1,6 @@
using Jellyfin2SamsungCrossOS.Services;
using Jellyfin2Samsung.Interfaces;
namespace Jellyfin2SamsungCrossOS.Extensions
namespace Jellyfin2Samsung.Extensions
{
public static class LocalizationExtensions
{

View File

@@ -1,4 +1,4 @@
namespace Jellyfin2SamsungCrossOS.Extensions
namespace Jellyfin2Samsung.Extensions
{
public delegate void ProgressCallback(string message);
}

View File

@@ -0,0 +1,32 @@
using System;
using System.Diagnostics;
using System.IO;
namespace Jellyfin2Samsung.Extensions
{
public sealed class FileTraceListener : TraceListener
{
private readonly string _filePath;
public FileTraceListener(string filePath)
{
_filePath = filePath;
Directory.CreateDirectory(Path.GetDirectoryName(_filePath)!);
}
public override void Write(string? message)
{
if (message == null) return;
File.AppendAllText(_filePath, message);
}
public override void WriteLine(string? message)
{
if (message == null) return;
File.AppendAllText(
_filePath,
$"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff} {message}{Environment.NewLine}");
}
}
}

View File

@@ -0,0 +1,215 @@
using Jellyfin2Samsung.Helpers.Core;
using Jellyfin2Samsung.Models;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
namespace Jellyfin2Samsung.Helpers.API
{
public class JellyfinApiClient
{
private readonly HttpClient _httpClient;
public JellyfinApiClient(HttpClient httpClient)
{
_httpClient = httpClient;
}
/// <summary>
/// Checks if a valid Jellyfin configuration exists with an authenticated user.
/// Uses AccessToken from username/password authentication.
/// </summary>
public static bool IsValidJellyfinConfiguration()
{
return !string.IsNullOrEmpty(AppSettings.Default.JellyfinFullUrl) &&
!string.IsNullOrEmpty(AppSettings.Default.JellyfinAccessToken) &&
!string.IsNullOrEmpty(AppSettings.Default.JellyfinUserId) &&
UrlHelper.IsValidHttpUrl($"{AppSettings.Default.JellyfinFullUrl}/Users");
}
/// <summary>
/// Checks if the user has a valid authentication (AccessToken + UserId).
/// </summary>
public static bool HasValidAuthentication()
{
return !string.IsNullOrEmpty(AppSettings.Default.JellyfinAccessToken) &&
!string.IsNullOrEmpty(AppSettings.Default.JellyfinUserId);
}
public async Task<List<JellyfinPluginInfo>> GetInstalledPluginsAsync(string serverUrl)
{
var list = new List<JellyfinPluginInfo>();
try
{
string url = UrlHelper.CombineUrl(serverUrl, "/Plugins");
Trace.WriteLine("Fetching installed plugins from: " + url);
var json = await _httpClient.GetStringAsync(url);
var parsed = JsonSerializer.Deserialize<List<JellyfinPluginInfo>>(json, JsonSerializerOptionsProvider.Default);
if (parsed != null)
list.AddRange(parsed);
}
catch (Exception ex)
{
Trace.WriteLine("Failed to fetch /Plugins: " + ex);
}
return list;
}
public async Task<JellyfinPublicSystemInfo?> GetPublicSystemInfoAsync(string serverUrl)
{
try
{
string url = UrlHelper.CombineUrl(serverUrl, "/System/Info/Public");
Trace.WriteLine("Fetching Jellyfin public system info from: " + url);
var json = await _httpClient.GetStringAsync(url);
return JsonSerializer.Deserialize<JellyfinPublicSystemInfo>(json, JsonSerializerOptionsProvider.Default);
}
catch (Exception ex)
{
Trace.WriteLine("Failed to fetch /System/Info/Public: " + ex);
return null;
}
}
/// <summary>
/// Sets up HTTP headers for authenticated Jellyfin API requests.
/// Uses the AccessToken obtained from username/password authentication.
/// </summary>
private void SetupHeaders()
{
_httpClient.DefaultRequestHeaders.Clear();
_httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(Constants.Api.UserAgent);
_httpClient.DefaultRequestHeaders.Add("Authorization",
string.Format(Constants.Api.MediaBrowserAuthHeader, AppSettings.Default.JellyfinAccessToken));
}
/// <summary>
/// Authenticates with Jellyfin using username and password.
/// Returns the access token, user ID, and admin status on success.
/// </summary>
public async Task<(string? accessToken, string? userId, bool isAdmin, string? error)> AuthenticateAsync(string username, string password)
{
try
{
var serverUrl = UrlHelper.NormalizeServerUrl(AppSettings.Default.JellyfinFullUrl);
var authUrl = $"{serverUrl}/Users/AuthenticateByName";
_httpClient.DefaultRequestHeaders.Clear();
_httpClient.DefaultRequestHeaders.Add("X-Emby-Authorization", Constants.Api.EmbyAuthHeader);
var authPayload = new
{
Username = username,
Pw = password
};
var json = JsonSerializer.Serialize(authPayload);
using var content = new StringContent(json, Encoding.UTF8, Constants.Api.JsonContentType);
var response = await _httpClient.PostAsync(authUrl, content);
if (response.IsSuccessStatusCode)
{
var responseJson = await response.Content.ReadAsStringAsync();
var authResponse = JsonNode.Parse(responseJson);
var accessToken = authResponse?["AccessToken"]?.GetValue<string>();
var userId = authResponse?["User"]?["Id"]?.GetValue<string>();
var isAdmin = authResponse?["User"]?["Policy"]?["IsAdministrator"]?.GetValue<bool>() ?? false;
Trace.WriteLine($"[Auth] User authenticated. IsAdmin: {isAdmin}");
return (accessToken, userId, isAdmin, null);
}
else
{
var errorContent = await response.Content.ReadAsStringAsync();
Trace.WriteLine($"Authentication failed: {response.StatusCode} - {errorContent}");
return (null, null, false, $"Authentication failed: {response.StatusCode}");
}
}
catch (Exception ex)
{
Trace.WriteLine($"Authentication error: {ex}");
return (null, null, false, ex.Message);
}
}
/// <summary>
/// Loads all Jellyfin users. Requires admin authentication.
/// </summary>
public async Task<List<JellyfinUser>> LoadUsersAsync()
{
var users = new List<JellyfinUser>();
try
{
SetupHeaders();
var serverUrl = UrlHelper.NormalizeServerUrl(AppSettings.Default.JellyfinFullUrl);
var usersUrl = $"{serverUrl}/Users";
Trace.WriteLine($"[LoadUsers] Fetching users from: {usersUrl}");
var response = await _httpClient.GetAsync(usersUrl);
if (response.IsSuccessStatusCode)
{
var responseJson = await response.Content.ReadAsStringAsync();
var usersArray = JsonNode.Parse(responseJson)?.AsArray();
if (usersArray != null)
{
foreach (var userNode in usersArray)
{
var user = new JellyfinUser
{
Id = userNode?["Id"]?.GetValue<string>() ?? "",
Name = userNode?["Name"]?.GetValue<string>() ?? ""
};
if (!string.IsNullOrEmpty(user.Id) && !string.IsNullOrEmpty(user.Name))
{
users.Add(user);
}
}
Trace.WriteLine($"[LoadUsers] Loaded {users.Count} users");
}
}
else
{
Trace.WriteLine($"[LoadUsers] Failed to load users: {response.StatusCode}");
}
}
catch (Exception ex)
{
Trace.WriteLine($"[LoadUsers] Error loading users: {ex}");
}
return users;
}
/// <summary>
/// Tests if the current server URL is reachable by checking the parameter url endpoint.
/// </summary>
public async Task<bool> TestServerConnectionAsync(string testUrl)
{
try
{
_httpClient.DefaultRequestHeaders.Clear();
var response = await _httpClient.GetAsync(testUrl);
return response.IsSuccessStatusCode;
}
catch
{
return false;
}
}
}
}

View File

@@ -0,0 +1,90 @@
using Jellyfin2Samsung.Helpers.Core;
using Jellyfin2Samsung.Interfaces;
using Jellyfin2Samsung.Models;
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
namespace Jellyfin2Samsung.Helpers.API
{
public class TizenApiClient
{
private readonly IDialogService _dialogService;
private readonly HttpClient _httpClient;
public TizenApiClient(
HttpClient httpClient,
IDialogService dialogService)
{
_dialogService = dialogService;
_httpClient = httpClient;
}
public async Task<NetworkDevice> GetDeveloperInfoAsync(NetworkDevice device)
{
try
{
string url = $"http://{device.IpAddress}:{Constants.Ports.SamsungTvApiPort}/api/v2/";
var response = await _httpClient.GetAsync(url);
response.EnsureSuccessStatusCode();
string jsonContent = await response.Content.ReadAsStringAsync();
var jsonObject = JsonNode.Parse(jsonContent);
var logFilePath = Path.Combine(AppContext.BaseDirectory, "Logs", $"debug_tv_api_{DateTime.Now:yyyy-MM-dd_HH-mm-ss-fff}.log");
await File.WriteAllTextAsync(logFilePath, jsonContent);
var deviceNode = jsonObject?["device"];
if (deviceNode == null)
{
return CreateFallbackDevice(device);
}
return new NetworkDevice
{
IpAddress = deviceNode["ip"]?.GetValue<string>() ?? device.IpAddress,
DeviceName = WebUtility.HtmlDecode(deviceNode["name"]?.GetValue<string>() ?? string.Empty),
ModelName = deviceNode["modelName"]?.GetValue<string>() ?? string.Empty,
Manufacturer = deviceNode["type"]?.GetValue<string>() ?? string.Empty,
DeveloperMode = deviceNode["developerMode"]?.GetValue<string>() ?? string.Empty,
DeveloperIP = deviceNode["developerIP"]?.GetValue<string>() ?? string.Empty
};
}
catch (HttpRequestException ex)
{
await _dialogService.ShowErrorAsync(
$"Error connecting to Samsung TV at {device.IpAddress}: {ex.Message}");
}
catch (JsonException ex)
{
await _dialogService.ShowErrorAsync(
$"Error parsing JSON response: {ex.Message}");
}
catch (Exception ex)
{
await _dialogService.ShowErrorAsync(
$"Unexpected error: {ex.Message}");
}
return CreateFallbackDevice(device);
}
private static NetworkDevice CreateFallbackDevice(NetworkDevice device)
{
return new NetworkDevice
{
IpAddress = device.IpAddress,
DeviceName = device.DeviceName,
Manufacturer = device.Manufacturer,
DeveloperMode = string.Empty,
DeveloperIP = string.Empty
};
}
}
}

View File

@@ -1,35 +1,52 @@
using Jellyfin2SamsungCrossOS.Models;
using Jellyfin2Samsung.Models;
using System;
using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Jellyfin2SamsungCrossOS.Helpers
namespace Jellyfin2Samsung.Helpers
{
public class AppSettings
{
private const string FileName = "settings.json";
private static readonly string FilePath =
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Jellyfin2SamsungCrossOS", FileName);
public static readonly string FolderPath = AppContext.BaseDirectory;
public static readonly string FilePath = Path.Combine(FolderPath, FileName);
public static readonly string TizenSdbPath = Path.Combine(FolderPath, "Assets", "TizenSDB");
public static readonly string CertificatePath = Path.Combine(FolderPath, "Assets", "Certificate");
public static readonly string ProfilePath = Path.Combine(FolderPath, "Assets", "TizenProfile");
public static readonly string EsbuildPath = Path.Combine(FolderPath, "Assets", "esbuild");
public static readonly string DownloadPath = Path.Combine(FolderPath, "Downloads");
private static AppSettings? _instance;
// --- Runtime-only cached object (not saved to disk) ---
[JsonIgnore]
public ExistingCertificates? ChosenCertificates { get; set; }
[JsonIgnore]
public string CustomWgtPath { get; set; } = "";
[JsonIgnore]
public string LocalIp { get; set; } = "";
[JsonIgnore]
public string TvIp { get; set; } = "";
public static AppSettings Default => _instance ??= Load();
// ----- User-scoped settings -----
public string Language { get; set; } = "en";
public string Certificate { get; set; } = "Jelly2Sams";
public bool RememberCustomIP { get; set; } = false;
public string CustomWgtPath { get; set; } = "";
public bool DeletePreviousInstall { get; set; } = false;
public string UserCustomIP { get; set; } = "";
public string SavedNetworkInterfaceName { get; set; } = "";
public bool ForceSamsungLogin { get; set; } = false;
public bool RTLReading { get; set; } = false;
public string JellyfinIP { get; set; } = "";
public string JellyfinBasePath { get; set; } = "";
public string ServerInputMode { get; set; } = "IP : Port";
public string JellyfinUsername { get; set; } = "";
public string JellyfinPassword { get; set; } = "";
public string JellyfinAccessToken { get; set; } = "";
public string JellyfinServerId { get; set; } = "";
public string JellyfinServerLocalAddress { get; set; } = "";
public string JellyfinServerName { get; set; } = "";
public string AudioLanguagePreference { get; set; } = "";
public string SubtitleLanguagePreference { get; set; } = "";
public bool EnableBackdrops { get; set; } = false;
@@ -41,31 +58,64 @@ namespace Jellyfin2SamsungCrossOS.Helpers
public bool NextUpEnabled { get; set; } = false;
public bool EnableExternalVideoPlayers { get; set; } = false;
public bool SkipIntros { get; set; } = false;
public bool AutoPlayNextEpisode { get; set; } = true;
public bool RememberAudioSelections { get; set; } = true;
public bool RememberSubtitleSelections { get; set; } = true;
public bool PlayDefaultAudioTrack { get; set; } = true;
public string SelectedTheme { get; set; } = "dark";
public string SelectedSubtitleMode { get; set; } = "Default";
public string ConfigUpdateMode { get; set; } = "None";
public string JellyfinApiKey { get; set; } = "";
public string JellyfinUserId { get; set; } = "";
public bool UserAutoLogin { get; set; } = true;
public bool IsJellyfinAdmin { get; set; } = false;
public string SelectedUserIds { get; set; } = ""; // Comma-separated list of selected user IDs for multi-user config
public string DistributorsEndpoint_V1 { get; set; } = "https://svdca.samsungqbe.com/apis/v1/distributors";
public string DistributorsEndpoint_V3 { get; set; } = "https://svdca.samsungqbe.com/apis/v3/distributors";
public string AuthorEndpoint_V3 { get; set; } = "https://svdca.samsungqbe.com/apis/v3/authors";
public bool TryOverwrite { get; set; } = true;
public bool UseServerScripts { get; set; } = false;
public bool OpenAfterInstall { get; set; } = false;
public bool EnableDevLogs { get; set; } = false;
public bool KeepWGTFile { get; set; } = false;
public bool PatchYoutubePlugin { get; set; } = false;
public string CustomCss { get; set; } = "";
public bool DarkMode { get; set; } = false;
public string GitHubToken { get; set; } = "";
public string LocalYoutubeServer { get; set; } = string.Empty;
// ----- Updater settings -----
public bool CheckForUpdatesOnStartup { get; set; } = true;
public string SkippedUpdateVersion { get; set; } = string.Empty;
public DateTime? LastUpdateCheck { get; set; } = null;
// ----- Application-scoped settings (readonly at runtime) -----
public string ReleasesUrl { get; set; } = "https://api.github.com/repos/jeppevinkel/jellyfin-tizen-builds/releases";
public string AuthorEndpoint { get; set; } = "https://dev.tizen.samsung.com/apis/v2/authors";
public string AppVersion { get; set; } = "v1.8.3.1";
public string TizenCliWindows { get; set; } = "https://download.tizen.org/sdk/Installer/tizen-studio_6.1/web-cli_Tizen_Studio_6.1_windows-64.exe";
public string TizenCliLinux { get; set; } = "https://download.tizen.org/sdk/Installer/tizen-studio_6.1/web-cli_Tizen_Studio_6.1_ubuntu-64.bin";
public string TizenCliMac { get; set; } = "https://download.tizen.org/sdk/Installer/tizen-studio_6.1/web-cli_Tizen_Studio_6.1_macos-64.bin";
public string JellyfinAvRelease { get; set; } = "https://api.github.com/repos/PatrickSt1991/Samsung-Jellyfin-Installer/releases/239769070";
public string AppVersion { get; set; } = "v2.2.0.8";
public string TizenSdb { get; set; } = "https://api.github.com/repos/PatrickSt1991/tizen-sdb/releases";
public string JellyfinAvRelease { get; set; } = "https://api.github.com/repos/PatrickSt1991/tizen-jellyfin-avplay/releases";
public string JellyfinAvReleaseFork { get; set; } = "https://api.github.com/repos/asamahy/tizen-jellyfin-avplay/releases";
public string JellyfinLegacy { get; set; } = "https://api.github.com/repos/jeppevinkel/jellyfin-tizen-builds/releases/tags/2024-10-27-1821";
public string CommunityRelease { get; set; } = "https://api.github.com/repos/PatrickSt1991/tizen-community-packages/releases";
public string MoonfinRelease { get; set; } = "https://api.github.com/repos/Moonfin-Client/Smart-TV/releases";
public string LiteFinRelease { get; set; } = "https://api.github.com/repos/MoazSalem/litefin/releases";
public string ReleaseInfo { get; set; } = "https://raw.githubusercontent.com/jeppevinkel/jellyfin-tizen-builds/refs/heads/master/README.md";
public string CommunityInfo { get; set; } = "https://raw.githubusercontent.com/PatrickSt1991/tizen-community-packages/refs/heads/main/README.md";
public AppSettings() { }
/// <summary>
/// Gets the full Jellyfin URL including base path for reverse proxy setups.
/// Example: https://xxx.seedhost.eu/xxx/jellyfin
/// </summary>
[JsonIgnore]
public string JellyfinFullUrl
{
get
{
var baseUrl = Core.UrlHelper.NormalizeServerUrl(JellyfinIP);
var basePath = JellyfinBasePath?.Trim('/') ?? "";
if (string.IsNullOrEmpty(basePath))
return baseUrl;
return $"{baseUrl}/{basePath}";
}
}
public void Save()
{
try

View File

@@ -1,180 +0,0 @@
using Jellyfin2SamsungCrossOS.Models;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography.X509Certificates;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace Jellyfin2SamsungCrossOS.Helpers
{
public class CertificateHelper
{
public List<ExistingCertificates> GetAvailableCertificates(string profilePath, string tizenCrypto)
{
var certificates = new List<ExistingCertificates>();
var cipherUtil = new CipherUtil();
// Default item
certificates.Add(new ExistingCertificates
{
Name = "Jelly2Sams (default)",
Duid = string.Empty,
File = null,
ExpireDate = null
});
if (!File.Exists(profilePath))
return certificates;
try
{
var doc = XDocument.Load(profilePath);
var profiles = doc.Root?.Elements("profile");
if (profiles == null)
return certificates;
foreach (var profile in profiles)
{
string? name = profile.Attribute("name")?.Value;
if (string.IsNullOrWhiteSpace(name))
continue;
// Author Certificate
var authorItem = profile.Elements("profileitem")
.FirstOrDefault(p => p.Attribute("distributor")?.Value == "0");
string? keyPath = authorItem?.Attribute("key")?.Value;
string? encryptedPassword = authorItem?.Attribute("password")?.Value;
DateTime? expireDate = null;
string? decryptedPassword = null;
if (!string.IsNullOrWhiteSpace(keyPath) && File.Exists(keyPath) && !string.IsNullOrEmpty(encryptedPassword))
{
if (File.Exists(encryptedPassword))
decryptedPassword = cipherUtil.RunWincryptDecrypt(encryptedPassword, tizenCrypto);
else if (IsBase64String(encryptedPassword))
decryptedPassword = cipherUtil.GetDecryptedString(encryptedPassword);
else
continue;
try
{
var cert = new X509Certificate2(keyPath, decryptedPassword, X509KeyStorageFlags.Exportable);
expireDate = cert.NotAfter;
}
catch (Exception ex)
{
Debug.WriteLine($"Failed to read certificate '{keyPath}': {ex.Message}");
}
if (expireDate.HasValue && expireDate.Value.Date >= DateTime.Today)
{
// Retrieve distributor certificate for DUID
string duid = ExtractDistributorDuid(profile, cipherUtil, tizenCrypto);
certificates.Add(new ExistingCertificates
{
Name = name,
File = keyPath,
ExpireDate = expireDate,
Duid = duid
});
}
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error reading profile XML: {ex.Message}");
}
return certificates;
}
private string ExtractDistributorDuid(XElement profile, CipherUtil cipherUtil, string tizenCrypto)
{
List<string> duids = new List<string>();
var distributorItem = profile.Elements("profileitem")
.FirstOrDefault(p => p.Attribute("distributor")?.Value == "1");
if (distributorItem == null)
return string.Empty;
string? keyPath = distributorItem.Attribute("key")?.Value;
string? encryptedPassword = distributorItem.Attribute("password")?.Value;
string? decryptedPassword = null;
if (!string.IsNullOrWhiteSpace(keyPath) && File.Exists(keyPath) && !string.IsNullOrEmpty(encryptedPassword))
{
if (File.Exists(encryptedPassword))
decryptedPassword = cipherUtil.RunWincryptDecrypt(encryptedPassword, tizenCrypto);
else if (IsBase64String(encryptedPassword))
decryptedPassword = cipherUtil.GetDecryptedString(encryptedPassword);
try
{
var distributorCert = new X509Certificate2(keyPath, decryptedPassword, X509KeyStorageFlags.Exportable);
foreach (var ext in distributorCert.Extensions)
{
var raw = ext.Format(true);
foreach (Match match in Regex.Matches(raw, @"URN:tizen:deviceid=([A-Za-z0-9]+)"))
{
string duid = match.Groups[1].Value;
if (!duids.Contains(duid))
duids.Add(duid);
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"Failed to read distributor certificate '{keyPath}': {ex.Message}");
}
}
return string.Join(",", duids); // ✅ Return comma-separated list
}
public static bool IsBase64String(string s)
{
s = s.Trim();
return (s.Length % 4 == 0) &&
Regex.IsMatch(s, @"^[A-Za-z0-9\+/]*={0,2}$");
}
public async Task HandleErrorResponse(HttpResponseMessage response)
{
if (response.StatusCode == System.Net.HttpStatusCode.Forbidden)
{
throw new Exception("You've made too many requests in a given amount of time.\nPlease wait and try your request again later.");
}
try
{
var errorContent = await response.Content.ReadAsStringAsync();
if (!string.IsNullOrEmpty(errorContent))
{
var errorJson = JsonSerializer.Deserialize<JsonElement>(errorContent);
if (errorJson.TryGetProperty("error", out var errorObj))
{
var name = errorObj.TryGetProperty("name", out var nameEl) ? nameEl.ToString() : "";
var status = errorObj.TryGetProperty("status", out var statusEl) ? statusEl.ToString() : "";
var code = errorObj.TryGetProperty("code", out var codeEl) ? codeEl.ToString() : "";
var description = errorObj.TryGetProperty("description", out var descEl) ? descEl.ToString() : "";
throw new Exception($"Samsung API Error - Name: {name}, Status: {status}, Code: {code}, Description: {description}");
}
}
}
catch (JsonException)
{
}
throw new Exception($"Server response code: {response.StatusCode}");
}
}
}

View File

@@ -1,159 +0,0 @@
using System;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace Jellyfin2SamsungCrossOS.Helpers
{
public class CipherUtil
{
private const string FallbackKeyString = "KYANINYLhijklmnopqrstuvwx";
private string _usedPassword = FallbackKeyString;
private byte[] KeyBytes => Encoding.UTF8.GetBytes(_usedPassword).Take(24).ToArray();
public async Task<string> ExtractPasswordAsync(string jarPath)
{
string? extracted = await TryExtractFromJarAsync(jarPath);
if (!string.IsNullOrEmpty(extracted))
{
_usedPassword = extracted;
return extracted;
}
_usedPassword = FallbackKeyString;
return FallbackKeyString;
}
private async Task<string?> TryExtractFromJarAsync(string jarPath)
{
try
{
var jarFiles = Directory.GetFiles(jarPath, "*.jar");
foreach (var jar in jarFiles)
{
string fileName = Path.GetFileName(jar);
if (!fileName.StartsWith("org.tizen.common.cert") || !fileName.EndsWith(".jar"))
continue;
using var fs = File.OpenRead(jar);
using var ms = new MemoryStream();
await fs.CopyToAsync(ms);
ms.Position = 0;
using var zip = new ZipArchive(ms, ZipArchiveMode.Read);
foreach (var entry in zip.Entries)
{
if (!entry.FullName.EndsWith("CipherUtil.class", StringComparison.OrdinalIgnoreCase))
continue;
using var classStream = entry.Open();
var password = ExtractPasswordFromClassSimple(classStream);
if (!string.IsNullOrEmpty(password))
return password;
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"Cipher extraction failed: {ex.Message}");
}
return null;
}
private string? ExtractPasswordFromClassSimple(Stream classStream)
{
try
{
using var reader = new StreamReader(classStream);
string content = reader.ReadToEnd();
string knownPassword = FallbackKeyString;
int index = content.IndexOf(knownPassword, StringComparison.Ordinal);
if (index != -1)
{
string extracted = content.Substring(index, knownPassword.Length);
if (extracted.All(char.IsLetterOrDigit) && extracted.Length == 26)
return extracted;
}
}
catch { }
return null;
}
public string GetEncryptedString(string plainText)
{
using var tdes = new TripleDESCryptoServiceProvider
{
Key = KeyBytes,
Mode = CipherMode.ECB,
Padding = PaddingMode.PKCS7
};
byte[] encrypted = tdes.CreateEncryptor().TransformFinalBlock(Encoding.UTF8.GetBytes(plainText), 0, plainText.Length);
return Convert.ToBase64String(encrypted);
}
public string GetDecryptedString(string encryptedBase64)
{
byte[] encryptedBytes = Convert.FromBase64String(encryptedBase64);
using var tripleDes = TripleDES.Create();
tripleDes.Key = KeyBytes;
tripleDes.Mode = CipherMode.ECB;
tripleDes.Padding = PaddingMode.PKCS7;
byte[] decrypted = tripleDes.CreateDecryptor().TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length);
return Encoding.UTF8.GetString(decrypted);
}
public string GenerateRandomPassword(int length = 12)
{
if (length < 8)
throw new ArgumentException("Password must be at least 8 characters long.");
const string upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const string lower = "abcdefghijklmnopqrstuvwxyz";
const string digits = "0123456789";
const string all = upper + lower + digits;
var randomBytes = new byte[length];
using var rng = RandomNumberGenerator.Create();
rng.GetBytes(randomBytes);
var chars = new char[length];
chars[0] = upper[randomBytes[0] % upper.Length];
chars[1] = lower[randomBytes[1] % lower.Length];
chars[2] = digits[randomBytes[2] % digits.Length];
for (int i = 3; i < length; i++)
chars[i] = all[randomBytes[i] % all.Length];
return new string(chars.OrderBy(_ => Guid.NewGuid()).ToArray());
}
public string RunWincryptDecrypt(string filePath, string cryptoPath)
{
var psi = new ProcessStartInfo
{
FileName = cryptoPath,
Arguments = $"--decrypt \"{filePath}\"",
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
};
using var process = Process.Start(psi)!;
string output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
return output.Split(new[] { "PASSWORD:" }, StringSplitOptions.None)[1].Trim();
}
}
}

View File

@@ -0,0 +1,54 @@
using Avalonia.Data.Converters;
using Avalonia.Media;
using Jellyfin2Samsung.Models;
using System;
using System.Globalization;
namespace Jellyfin2Samsung.Helpers.Converters
{
public class TvLogStatusToBrushConverter : IValueConverter
{
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
=> value switch
{
TvLogConnectionStatus.Connected => new SolidColorBrush(Color.Parse("#27AE60")),
TvLogConnectionStatus.Listening => new SolidColorBrush(Color.Parse("#2980B9")),
TvLogConnectionStatus.NoConnections => new SolidColorBrush(Color.Parse("#E67E22")),
TvLogConnectionStatus.Stopped => new SolidColorBrush(Color.Parse("#7F8C8D")),
_ => Brushes.Gray
};
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
=> throw new NotSupportedException();
}
public class StringEqualsConverter : IValueConverter
{
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is not GitHubRelease release)
return false;
return release.Name == parameter?.ToString();
}
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
=> throw new NotSupportedException();
}
public class StringNotEqualsConverter : IValueConverter
{
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is not GitHubRelease release)
return true;
return release.Name != parameter?.ToString();
}
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
=> throw new NotSupportedException();
}
}

View File

@@ -0,0 +1,103 @@
using Jellyfin2Samsung.Models;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
namespace Jellyfin2Samsung.Helpers.Core
{
public class AddLatestRelease
{
private readonly HttpClient _httpClient;
public AddLatestRelease(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<List<GitHubRelease>> GetReleasesAsync(string url, string prefix, string displayName, int take = 1)
{
if (take < 1) take = 1;
try
{
using var response = await _httpClient.GetAsync(url);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
try
{
var releases = JsonSerializer.Deserialize<List<GitHubRelease>>(
json,
JsonSerializerOptionsProvider.Default);
if (releases == null || releases.Count == 0)
return new List<GitHubRelease>();
releases = releases
.Select(r =>
{
r.Assets = r.Assets?
.Where(a =>
!string.IsNullOrWhiteSpace(a.FileName) &&
(a.FileName.EndsWith(".wgt", StringComparison.OrdinalIgnoreCase) ||
a.FileName.EndsWith(".tpk", StringComparison.OrdinalIgnoreCase)))
.ToList() ?? new List<Asset>();
return r;
})
.Where(r => r.Assets.Count > 0)
.ToList();
if (releases.Count == 0)
return new List<GitHubRelease>();
var result = releases.Count > take ? releases.GetRange(0, take) : releases;
foreach (var r in result)
{
r.Name = string.IsNullOrWhiteSpace(displayName)
? $"{prefix}{r.Name}"
: displayName;
}
return result;
}
catch (JsonException)
{
var latest = JsonSerializer.Deserialize<GitHubRelease>(
json,
JsonSerializerOptionsProvider.Default);
if (latest == null)
return new List<GitHubRelease>();
latest.Assets = latest.Assets?
.Where(a =>
!string.IsNullOrWhiteSpace(a.FileName) &&
(a.FileName.EndsWith(".wgt", StringComparison.OrdinalIgnoreCase) ||
a.FileName.EndsWith(".tpk", StringComparison.OrdinalIgnoreCase)))
.ToList() ?? new List<Asset>();
if (latest.Assets.Count == 0)
return new List<GitHubRelease>();
latest.Name = string.IsNullOrWhiteSpace(displayName)
? $"{prefix}{latest.Name}"
: displayName;
return new List<GitHubRelease> { latest };
}
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine($"Failed to fetch release from {url}: {ex}");
return new List<GitHubRelease>();
}
}
}
}

View File

@@ -0,0 +1,270 @@
namespace Jellyfin2Samsung.Helpers.Core
{
/// <summary>
/// Centralized constants for the application.
/// Eliminates magic strings and numbers scattered throughout the codebase.
/// </summary>
public static class Constants
{
/// <summary>
/// Application identifiers and names.
/// </summary>
public static class AppIdentifiers
{
public const string JellyfinAppName = "Jellyfin";
public const string Jelly2SamsDefault = "Jelly2Sams (default)";
public const string Jelly2Sams = "Jelly2Sams";
public const string CustomWgtFile = "Custom WGT File";
}
/// <summary>
/// preview image URLS for different apps.
/// </summary>
public static class PreviewImages
{
public const string Jellyfin = "https://jellyfin.org/assets/images/10.8-home-4a73a92bf90d1eeffa5081201ca9c7bb.png";
public const string Moonfin = "https://iili.io/fs8W4Re.png";
public const string Moonlight = "https://iili.io/fsvn6mJ.png";
public const string Fireplace = "https://raw.githubusercontent.com/thonythony/fireplace/refs/heads/master/icon.jpg";
public const string TVApp = "https://iili.io/fsvaHsn.png";
public const string Twitch = "https://iili.io/fsvUNu2.md.gif";
public const string ClubInfoBoard = "https://iili.io/fsviHQV.png";
public const string Doom = "https://iili.io/fyofVqu.png";
public const string TTD = "https://iili.io/qFBP2vn.png";
public const string Litefin = "https://iili.io/qerI0la.png";
}
/// <summary>
/// Tizen installation error codes returned by the SDB tool.
/// </summary>
public static class TizenErrorCodes
{
public const string DownloadFailed116 = "download failed[116]";
public const string InstallFailed118012 = "install failed[118012]";
public const string InstallFailed118Minus12 = "install failed[118, -12]";
public const string InstallFailed118 = "install failed[118]";
public const string Installing100 = "installing[100]";
public const string InstallCompleted = "install completed";
public const string ResignFailed = "Re-sign failed";
public const string Failed = "failed";
public const string NotInstalled = "failed[132]";
}
/// <summary>
/// Default values used throughout the application.
/// </summary>
public static class Defaults
{
public const string TizenOsVersion = "7.0";
public const string SdkToolPath = "/opt/usr/apps/tmp";
public const string HomeDeveloperPath = "/home/developer";
public const string TizenSdbDefaultVersion = "v1.0.0";
public const int SamsungLoginTimeoutMinutes = 5;
public const int NetworkScanTimeoutMs = 1000;
public const int HttpRequestTimeoutSeconds = 15;
public const int WebSocketMonitorDelaySeconds = 10;
}
/// <summary>
/// Tizen version thresholds for feature compatibility.
/// </summary>
public static class TizenVersions
{
public const string CertificateRequired = "7.0";
public const string PushInstallMax = "4.0";
public const string IntermediateVersion = "3.0";
}
/// <summary>
/// Network ports used by the application.
/// </summary>
public static class Ports
{
public const int TizenDevPort = 26101;
public const int SamsungTvApiPort = 8001;
public const int SamsungLoginCallbackPort = 4794;
}
/// <summary>
/// File extensions and patterns.
/// </summary>
public static class FilePatterns
{
public const string WgtExtension = ".wgt";
public const string TpkExtension = ".tpk";
public const string P12Extension = ".p12";
public const string CsrExtension = ".csr";
public const string CerExtension = ".cer";
public const string CrtExtension = ".crt";
public const string JsExtension = ".js";
public const string CssExtension = ".css";
public const string WgtPattern = "*.wgt";
public const string TpkPattern = "*.tpk";
}
/// <summary>
/// Platform-specific binary names.
/// </summary>
public static class PlatformBinaries
{
public const string TizenSdbWindowsPattern = "TizenSdb*.exe";
public const string TizenSdbLinuxPattern = "TizenSdb*_linux";
public const string TizenSdbMacOsPattern = "TizenSdb*_macos";
public const string WindowsExtension = ".exe";
public const string LinuxSuffix = "_linux";
public const string MacOsSuffix = "_macos";
public const string EsbuildWindows = "win-x64";
public const string EsbuildLinux = "linux-x64";
public const string EsbuildMacOs = "osx-universal";
public const string EsbuildExecutable = "esbuild";
public const string EsbuildExecutableWindows = "esbuild.exe";
}
/// <summary>
/// HTTP and API related constants.
/// </summary>
public static class Api
{
public const string UserAgent = "SamsungJellyfinInstaller/1.0";
public const string MediaBrowserAuthHeader = "MediaBrowser Token=\"{0}\"";
public const string EmbyAuthHeader = "MediaBrowser Client=\"Samsung Jellyfin Installer\", Device=\"PC\", DeviceId=\"samsungjellyfin\", Version=\"1.0.0\"";
public const string JsonContentType = "application/json";
}
/// <summary>
/// Samsung API endpoints and OAuth constants.
/// </summary>
public static class Samsung
{
public const string LoopbackHost = "localhost";
public const string CallbackPath = "/signin/callback";
public const string OAuthClientId = "v285zxnl3h";
public const string OAuthState = "accountcheckdogeneratedstatetext";
public const string TokenType = "TOKEN";
public const string SignInGateUrl = "https://account.samsung.com/accounts/be1dce529476c1a6d407c4c7578c31bd/signInGate";
public const string PlatformVd = "VD";
public const string PrivilegeLevelPublic = "Public";
public const string DeveloperTypeIndividual = "Individual";
}
/// <summary>
/// Certificate related constants.
/// </summary>
public static class Certificate
{
public const string AuthorFileName = "author.p12";
public const string DistributorFileName = "distributor.p12";
public const string PasswordFileName = "password.txt";
public const string DeviceProfileFileName = "device-profile.xml";
public const string AuthorCsrFileName = "author.csr";
public const string DistributorCsrFileName = "distributor.csr";
public const string SignedAuthorCerFileName = "signed_author.cer";
public const string SignedDistributorCerFileName = "signed_distributor.cer";
public const string AuthorCaFileName = "vd_tizen_dev_author_ca.cer";
public const string DistributorCaFileName = "vd_tizen_dev_public2.crt";
public const string KeyAlias = "usercertificate";
public const string CsrSubjectAuthor = "C=, ST=, L=, O=, OU=, CN=Jelly2Sams";
public const string CsrSubjectDistributorTemplate = "CN=TizenSDK, OU=, O=, L=, ST=, C=, emailAddress={0}";
public const string SigningAlgorithm = "SHA256withRSA";
public const int RsaKeySize = 2048;
}
/// <summary>
/// Jellyfin web app paths and file names.
/// </summary>
public static class JellyfinWeb
{
public const string IndexHtml = "index.html";
public const string ConfigJson = "config.json";
public const string WwwFolder = "www";
public const string PluginCacheFolder = "plugin_cache";
public const string CredentialsStorageKey = "jellyfin_credentials";
}
/// <summary>
/// Random string generation character sets.
/// </summary>
public static class CharacterSets
{
public const string AlphaLower = "abcdefghijklmnopqrstuvwxyz";
public const string AlphaUpper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
public const string Alpha = AlphaLower + AlphaUpper;
public const string AlphaNumeric = Alpha + "0123456789";
}
/// <summary>
/// Updater related constants.
/// </summary>
public static class Updater
{
public const string RepoOwner = "Jellyfin2Samsung";
public const string RepoName = "Samsung-Jellyfin-Installer";
public const string AtomFeedUrl = "https://github.com/Jellyfin2Samsung/Samsung-Jellyfin-Installer/releases.atom";
public const string ReleasesPageUrl = "https://github.com/Jellyfin2Samsung/Samsung-Jellyfin-Installer/releases";
public const string LatestReleaseApiUrl = "https://api.github.com/repos/Jellyfin2Samsung/Samsung-Jellyfin-Installer/releases/latest";
public const int UpdateCheckTimeoutSeconds = 10;
}
/// <summary>
/// Localization keys used for status messages.
/// </summary>
public static class LocalizationKeys
{
public const string InstallTizenSdb = "InstallTizenSdb";
public const string DiagnoseTv = "diagnoseTv";
public const string AlreadyInstalled = "alreadyInstalled";
public const string DeleteExistingVersion = "deleteExistingVersion";
public const string DeleteExistingFailed = "deleteExistingFailed";
public const string DeleteExistingSuccess = "deleteExistingSuccess";
public const string DeleteExistingNotAllowed = "deleteExistingNotAllowed";
public const string ConnectingToDevice = "ConnectingToDevice";
public const string TvNameNotFound = "TvNameNotFound";
public const string TvDuidNotFound = "TvDuidNotFound";
public const string SamsungLogin = "SamsungLogin";
public const string CreatingCertificateProfile = "CreatingCertificateProfile";
public const string PackageAndSign = "packageAndSign";
public const string InstallingPackage = "InstallingPackage";
public const string InstallationFailed = "InstallationFailed";
public const string InstallationSuccessful = "InstallationSuccessful";
public const string InsufficientSpace = "insufficientSpace";
public const string AuthorMismatch = "AuthorMismatch";
public const string ModifyConfigRequired = "modiyConfigRequired";
public const string FailedTizenSdb = "FailedTizenSdb";
public const string CheckingTizenSdb = "CheckingTizenSdb";
public const string ScanningNetwork = "ScanningNetwork";
public const string InitializationFailed = "InitializationFailed";
public const string NoDevicesFound = "NoDevicesFound";
public const string NoDevicesFoundRetry = "NoDevicesFoundRetry";
public const string Ready = "Ready";
public const string FailedLoadingReleases = "FailedLoadingReleases";
public const string InvalidDeviceIp = "InvalidDeviceIp";
public const string LblOther = "lblOther";
public const string IpNotListed = "IpNotListed";
public const string IncompatiblePackage = "IncompatiblePackage";
public const string IncompatiblePackageDetailed = "IncompatiblePackageDetailed";
// Updater localization keys
public const string UpdateAvailable = "UpdateAvailable";
public const string UpdateCurrentVersion = "UpdateCurrentVersion";
public const string UpdateLatestVersion = "UpdateLatestVersion";
public const string UpdateReleaseNotes = "UpdateReleaseNotes";
public const string UpdateManual = "UpdateManual";
public const string UpdateAutomatic = "UpdateAutomatic";
public const string UpdateSkip = "UpdateSkip";
public const string UpdateDownloading = "UpdateDownloading";
public const string UpdateApplying = "UpdateApplying";
public const string UpdateApplyingMessage = "UpdateApplyingMessage";
public const string UpdateError = "UpdateError";
public const string UpdateCheckFailed = "UpdateCheckFailed";
}
/// <summary>
/// Esbuild transpilation settings.
/// </summary>
public static class Esbuild
{
public const string TempFolderName = "J2S_Esbuild";
public const string TargetEs2015 = "es2015";
}
}
}

View File

@@ -0,0 +1,93 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading.Tasks;
namespace Jellyfin2Samsung.Helpers.Core
{
public static class EsbuildHelper
{
public static string? GetEsbuildPath()
{
try
{
string baseDir = AppContext.BaseDirectory;
return PlatformService.GetEsbuildPath(Path.Combine(baseDir, AppSettings.EsbuildPath));
}
catch
{
return null;
}
}
/// <summary>
/// Transpiles ES2015+ JavaScript to ES5 using esbuild.
/// If esbuild is missing or fails, returns the original JS.
/// </summary>
public static async Task<string> TranspileAsync(string js, string? relPathForLog = null)
{
try
{
string? esbuildPath = GetEsbuildPath();
if (string.IsNullOrEmpty(esbuildPath))
{
Trace.WriteLine($"esbuild binary not found, skipping transpile for {relPathForLog ?? "unknown"}");
return js;
}
string tempRoot = Path.Combine(Path.GetTempPath(), Constants.Esbuild.TempFolderName);
Directory.CreateDirectory(tempRoot);
string inputPath = Path.Combine(tempRoot, Guid.NewGuid().ToString("N") + Constants.FilePatterns.JsExtension);
string outputPath = Path.Combine(tempRoot, Guid.NewGuid().ToString("N") + Constants.FilePatterns.JsExtension);
await File.WriteAllTextAsync(inputPath, js, Encoding.UTF8);
var psi = new ProcessStartInfo
{
FileName = esbuildPath,
Arguments = $"\"{inputPath}\" --outfile=\"{outputPath}\" --target={Constants.Esbuild.TargetEs2015}",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};
using var proc = new Process { StartInfo = psi };
proc.Start();
string stdout = await proc.StandardOutput.ReadToEndAsync();
string stderr = await proc.StandardError.ReadToEndAsync();
proc.WaitForExit();
if (proc.ExitCode != 0 || !File.Exists(outputPath))
{
Trace.WriteLine($"esbuild failed for {relPathForLog ?? "unknown"} (exit {proc.ExitCode}): {stderr}");
return js;
}
string transpiled = await File.ReadAllTextAsync(outputPath, Encoding.UTF8);
try
{
File.Delete(inputPath);
File.Delete(outputPath);
}
catch
{
// ignore cleanup errors
}
Trace.WriteLine($"Transpiled {relPathForLog ?? "unknown"} via esbuild");
return transpiled;
}
catch (Exception ex)
{
Trace.WriteLine($"esbuild transpile error for {relPathForLog ?? "unknown"}: {ex}");
return js;
}
}
}
}

View File

@@ -0,0 +1,212 @@
using Avalonia.Platform.Storage;
using Jellyfin2Samsung.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Jellyfin2Samsung.Helpers.Core
{
public class FileHelper
{
private static readonly string[] wgtItem = ["*.wgt"];
private static readonly string[] tpkItem = ["*.tpk"];
private static readonly string[] allItem = ["*.wgt", "*.tpk"];
public async Task<string?> BrowseWgtFilesAsync(IStorageProvider storageProvider)
{
var fileTypes = new List<FilePickerFileType>
{
new("WGT Files")
{
Patterns = wgtItem
},
new("TPK Files")
{
Patterns = tpkItem
},
new("All Supported Files")
{
Patterns = allItem
}
};
var options = new FilePickerOpenOptions
{
Title = "Select WGT/TPK File",
FileTypeFilter = fileTypes,
AllowMultiple = true
};
var files = await storageProvider.OpenFilePickerAsync(options);
if (files?.Any() == true)
return string.Join(";", files.Select(f => f.Path.LocalPath));
return null;
}
public List<ExtensionEntry> ParseExtensions(string output)
{
var extensions = new List<ExtensionEntry>();
foreach (Match match in RegexPatterns.Extension.ExtensionEntry.Matches(output))
{
extensions.Add(new ExtensionEntry
{
Index = int.Parse(match.Groups[1].Value),
Name = match.Groups[2].Value.Trim(),
Activated = bool.Parse(match.Groups[3].Value)
});
}
return extensions;
}
public static async Task<string?> ReadWgtPackageId(string wgtPath)
{
if (!File.Exists(wgtPath))
return null;
using var memoryStream = new MemoryStream();
using (var originalStream = File.OpenRead(wgtPath))
await originalStream.CopyToAsync(memoryStream);
memoryStream.Position = 0;
using var archive = new ZipArchive(memoryStream, ZipArchiveMode.Read, true);
var configEntry = archive.GetEntry("config.xml");
if (configEntry == null)
return null;
string configContent;
using (var reader = new StreamReader(configEntry.Open(), Encoding.UTF8))
configContent = await reader.ReadToEndAsync();
var match = RegexPatterns.WgtConfig.TizenApplicationId.Match(configContent);
return match.Success ? match.Groups["pkg"].Value : null;
}
public static async Task<string?> ReadExtractedWgtPackageId(string workspaceRoot)
{
var configPath = Path.Combine(workspaceRoot, "config.xml");
if (!File.Exists(configPath))
return null;
var configContent = await File.ReadAllTextAsync(configPath, Encoding.UTF8);
var match = RegexPatterns.WgtConfig.TizenApplicationId.Match(configContent);
return match.Success ? match.Groups["pkg"].Value : null;
}
public static async Task<bool> ModifyWgtPackageId(string wgtPath)
{
if (!File.Exists(wgtPath))
return false;
var oldPkg = await ReadWgtPackageId(wgtPath);
if (string.IsNullOrEmpty(oldPkg))
return false;
var newPkg = GenerateRandomString(oldPkg.Length);
using var memoryStream = new MemoryStream();
using (var originalStream = File.OpenRead(wgtPath))
await originalStream.CopyToAsync(memoryStream);
memoryStream.Position = 0;
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Update, true))
{
var configEntry = archive.GetEntry("config.xml");
if (configEntry == null)
return false;
string configContent;
using (var reader = new StreamReader(configEntry.Open(), Encoding.UTF8))
configContent = await reader.ReadToEndAsync();
// Replace old package ID with the new one
var pattern = RegexPatterns.WgtConfig.CreatePackageIdReplacePattern(oldPkg);
var regex = new Regex(pattern, RegexOptions.Multiline);
var newConfig = regex.Replace(configContent, m =>
m.Value.Replace(oldPkg, newPkg)
);
// Replace entry inside ZIP
configEntry.Delete();
var newEntry = archive.CreateEntry("config.xml");
using (var writer = new StreamWriter(newEntry.Open(), Encoding.UTF8))
await writer.WriteAsync(newConfig);
}
await File.WriteAllBytesAsync(wgtPath, memoryStream.ToArray());
return true;
}
private static string GenerateRandomString(int length)
{
var sb = new StringBuilder(length);
for (int i = 0; i < length; i++)
sb.Append(Constants.CharacterSets.AlphaNumeric[Random.Shared.Next(Constants.CharacterSets.AlphaNumeric.Length)]);
return sb.ToString();
}
public static async Task<string?> ReadWgtApplicationId(string wgtPath)
{
if (!File.Exists(wgtPath))
return null;
using var memoryStream = new MemoryStream();
using (var originalStream = File.OpenRead(wgtPath))
await originalStream.CopyToAsync(memoryStream);
memoryStream.Position = 0;
using var archive = new ZipArchive(memoryStream, ZipArchiveMode.Read, true);
var configEntry = archive.GetEntry("config.xml");
if (configEntry == null)
return null;
string configContent;
using (var reader = new StreamReader(configEntry.Open(), Encoding.UTF8))
configContent = await reader.ReadToEndAsync();
// Prefer using a RegexPatterns entry if you want; otherwise this is safe and specific:
var match = Regex.Match(
configContent,
@"<tizen:application\b[^>]*\bid\s*=\s*""(?<id>[^""]+)""",
RegexOptions.IgnoreCase);
return match.Success ? match.Groups["id"].Value : null;
}
public static async Task<string?> ReadWgtRequiredVersion(string wgtPath)
{
if (!File.Exists(wgtPath))
return null;
using var memoryStream = new MemoryStream();
using (var originalStream = File.OpenRead(wgtPath))
await originalStream.CopyToAsync(memoryStream);
memoryStream.Position = 0;
using var archive = new ZipArchive(memoryStream, ZipArchiveMode.Read, true);
var configEntry = archive.GetEntry("config.xml");
if (configEntry == null)
return null;
string configContent;
using (var reader = new StreamReader(configEntry.Open(), Encoding.UTF8))
configContent = await reader.ReadToEndAsync();
var match = Regex.Match(
configContent,
@"<tizen:application\b[^>]*\brequired_version\s*=\s*""(?<version>[^""]+)""",
RegexOptions.IgnoreCase);
return match.Success ? match.Groups["version"].Value : null;
}
}
}

View File

@@ -0,0 +1,94 @@
using System;
using System.Diagnostics;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
namespace Jellyfin2Samsung.Helpers.Core
{
public class GitHubAuthHandler : DelegatingHandler
{
private readonly string? _token;
public GitHubAuthHandler(string? token)
: base(new HttpClientHandler())
{
_token = token;
}
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (!string.IsNullOrEmpty(_token) && IsGitHubRequest(request.RequestUri))
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _token);
}
return base.SendAsync(request, cancellationToken);
}
private static bool IsGitHubRequest(Uri? uri)
{
if (uri == null) return false;
var host = uri.Host;
return host.Equals("api.github.com", StringComparison.OrdinalIgnoreCase)
|| host.Equals("raw.githubusercontent.com", StringComparison.OrdinalIgnoreCase)
|| host.Equals("github.com", StringComparison.OrdinalIgnoreCase);
}
public static string? ResolveToken(AppSettings settings)
{
// 1. Explicit setting
if (!string.IsNullOrWhiteSpace(settings.GitHubToken))
{
Trace.TraceInformation("[GitHubAuth] Using token from app settings");
return settings.GitHubToken.Trim();
}
// 2. Environment variable
var envToken = Environment.GetEnvironmentVariable("GITHUB_TOKEN");
if (!string.IsNullOrWhiteSpace(envToken))
{
Trace.TraceInformation("[GitHubAuth] Using token from GITHUB_TOKEN environment variable");
return envToken.Trim();
}
// 3. GitHub CLI (gh auth token)
try
{
var psi = new ProcessStartInfo
{
FileName = "gh",
Arguments = "auth token",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
using var process = Process.Start(psi);
if (process != null)
{
var output = process.StandardOutput.ReadToEnd().Trim();
process.WaitForExit(5000);
if (process.ExitCode == 0 && !string.IsNullOrWhiteSpace(output))
{
Trace.TraceInformation("[GitHubAuth] Using token from GitHub CLI (gh auth token)");
return output;
}
}
}
catch
{
// gh CLI not installed or not authenticated — ignore
}
// 4. No token available — unauthenticated requests
Trace.TraceInformation("[GitHubAuth] No token found — using unauthenticated requests");
return null;
}
}
}

View File

@@ -0,0 +1,92 @@
using System;
using System.Linq;
using System.Text.RegularExpressions;
namespace Jellyfin2Samsung.Helpers.Core
{
public static class HtmlUtils
{
public static string EnsureBaseHref(string html)
{
if (html.Contains("<base", System.StringComparison.OrdinalIgnoreCase))
return RegexPatterns.Html.BaseTag.Replace(html, "<base href=\".\">");
return html.Replace("<head>", "<head><base href=\".\">");
}
public static string RewriteLocalPaths(string html)
{
return RegexPatterns.Html.LocalPaths.Replace(html, "$1=\"$2\"");
}
public static string CleanAndApplyCsp(string html)
{
html = RegexPatterns.Html.CspMeta.Replace(html, "");
return html.Replace("</head>",
"<meta http-equiv=\"Content-Security-Policy\" content=\"default-src * 'unsafe-inline' 'unsafe-eval' data: blob:;\">\n</head>");
}
public static string EnsurePublicJsIsLast(string html)
{
const string tag = "<script src=\"plugin_cache/public.js\"></script>";
if (!html.Contains(tag)) return html;
html = html.Replace(tag, "");
return html.Replace("</body>", tag + "\n</body>");
}
public static string EscapeJsString(string html)
{
if (string.IsNullOrEmpty(html)) return "";
return html
.Replace("\\", "\\\\")
.Replace("'", "\\'")
.Replace("\"", "\\\"")
.Replace("\n", "\\n")
.Replace("\r", "\\r");
}
public static string RemoveMarkdownTable(string html)
{
if (string.IsNullOrEmpty(html))
return html;
var tablePattern = @"(\|[^\n]+\|\s*\n)+";
return Regex.Replace(html, tablePattern, string.Empty, RegexOptions.Multiline);
}
public static string StripHtml(string html)
{
if (string.IsNullOrEmpty(html))
return string.Empty;
// Simple HTML stripping - replace common tags
var text = html
.Replace("<br>", "\n")
.Replace("<br/>", "\n")
.Replace("<br />", "\n")
.Replace("</p>", "\n")
.Replace("</li>", "\n")
.Replace("<li>", "• ");
// Remove all remaining HTML tags
while (text.Contains('<') && text.Contains('>'))
{
var start = text.IndexOf('<');
var end = text.IndexOf('>', start);
if (end > start)
text = text.Remove(start, end - start + 1);
else
break;
}
// Decode common HTML entities
text = text
.Replace("&nbsp;", " ")
.Replace("&amp;", "&")
.Replace("&lt;", "<")
.Replace("&gt;", ">")
.Replace("&quot;", "\"")
.Replace("&#39;", "'");
// Clean up whitespace
var lines = text.Split('\n', StringSplitOptions.RemoveEmptyEntries);
return string.Join("\n", lines.Select(l => l.Trim()).Where(l => !string.IsNullOrEmpty(l)));
}
}
}

View File

@@ -0,0 +1,70 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Jellyfin2Samsung.Helpers.Core
{
/// <summary>
/// Provides centralized, pre-configured JsonSerializerOptions for consistent JSON serialization
/// throughout the application. This eliminates inconsistent settings and improves performance
/// by reusing options instances.
/// </summary>
public static class JsonSerializerOptionsProvider
{
/// <summary>
/// Default options for general JSON serialization/deserialization.
/// - Case-insensitive property matching
/// - Camel case naming policy
/// - Ignores null values when writing
/// </summary>
public static JsonSerializerOptions Default { get; } = new()
{
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
WriteIndented = false
};
/// <summary>
/// Options for pretty-printed JSON output (e.g., config files, logs).
/// Same as Default but with indentation enabled.
/// </summary>
public static JsonSerializerOptions Indented { get; } = new()
{
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
WriteIndented = true
};
/// <summary>
/// Options optimized for GitHub API responses.
/// Uses snake_case naming policy to match GitHub's JSON format.
/// </summary>
public static JsonSerializerOptions GitHub { get; } = new()
{
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
WriteIndented = false
};
/// <summary>
/// Options for strict JSON parsing (no case-insensitive matching).
/// Use when exact property name matching is required.
/// </summary>
public static JsonSerializerOptions Strict { get; } = new()
{
PropertyNameCaseInsensitive = false,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
WriteIndented = false
};
/// <summary>
/// Options for web/API responses with standard web conventions.
/// </summary>
public static JsonSerializerOptions Web { get; } = new(JsonSerializerDefaults.Web)
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
}
}

View File

@@ -1,33 +1,30 @@
using Jellyfin2SamsungCrossOS.Extensions;
using Jellyfin2SamsungCrossOS.Models;
using Jellyfin2SamsungCrossOS.Services;
using Avalonia.Controls.ApplicationLifetimes;
using Jellyfin2Samsung.Extensions;
using Jellyfin2Samsung.Interfaces;
using Jellyfin2Samsung.Models;
using Jellyfin2Samsung.ViewModels;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Jellyfin2SamsungCrossOS.Helpers
namespace Jellyfin2Samsung.Helpers.Core
{
public class PackageHelper
public class PackageHelper(
ITizenInstallerService tizenInstaller,
IDialogService dialogService,
INetworkService networkService)
{
private readonly ITizenInstallerService _tizenInstaller;
private readonly IDialogService _dialogService;
private readonly INetworkService _networkService;
private readonly ITizenInstallerService _tizenInstaller = tizenInstaller;
private readonly IDialogService _dialogService = dialogService;
private readonly INetworkService _networkService = networkService;
public PackageHelper(
ITizenInstallerService tizenInstaller,
IDialogService dialogService,
INetworkService networkService)
{
_tizenInstaller = tizenInstaller;
_dialogService = dialogService;
_networkService = networkService;
}
public async Task<string?> DownloadReleaseAsync(GitHubRelease release, Asset selectedAsset, ProgressCallback? progress = null)
public async Task<string?> DownloadReleaseAsync(GitHubRelease release, Asset? selectedAsset, ProgressCallback? progress = null)
{
if (release?.PrimaryDownloadUrl == null) return null;
if (selectedAsset?.DownloadUrl == null) return null;
try
{
@@ -38,18 +35,34 @@ namespace Jellyfin2SamsungCrossOS.Helpers
catch (Exception ex)
{
progress?.Invoke("DownloadFailed".Localized());
await _dialogService.ShowErrorAsync($"{"DownloadFailed".Localized()} {ex.Message}");
await _dialogService.ShowErrorAsync($"{"DownloadFailed".Localized()} {ex}");
return null;
}
}
public async Task<bool> InstallPackageAsync(string packagePath, NetworkDevice selectedDevice, ProgressCallback? progress = null)
public async Task<bool> InstallPackageAsync(string? packagePath, NetworkDevice selectedDevice, CancellationToken cancellationToken, ProgressCallback? progress = null, Action? onSamsungLoginStarted = null)
{
if(selectedDevice.DeveloperIP == null) return false;
var localIps = _networkService.GetRelevantLocalIPs()
.Select(ip => ip.ToString())
.ToList();
bool ipMismatch = !localIps.Contains(selectedDevice.DeveloperIP);
bool ipMismatch = !localIps.Contains(selectedDevice.DeveloperIP) && !string.IsNullOrEmpty(selectedDevice.DeveloperIP);
if (!string.IsNullOrEmpty(AppSettings.Default.LocalIp)
&& !string.IsNullOrEmpty(selectedDevice.DeveloperIP)
&& _networkService.IsDifferentSubnet(AppSettings.Default.LocalIp, selectedDevice.DeveloperIP))
{
bool continueExecution =
await _dialogService.ShowConfirmationAsync(
"Subnet Mismatch",
"subnetMismatch".Localized(),
"keyContinue".Localized(),
"keyStop".Localized());
if (!continueExecution)
return false;
}
if (string.IsNullOrEmpty(packagePath) || !File.Exists(packagePath))
{
@@ -84,7 +97,26 @@ namespace Jellyfin2SamsungCrossOS.Helpers
if (ipMismatch)
{
bool continueExecution = await _dialogService.ShowConfirmationAsync("IP Mismatch","DeveloperIPMismatch".Localized(), "keyContinue".Localized(), "keyStop".Localized());
bool isReversedIp = localIps
.Select(ip => _networkService.InvertIPAddress(ip))
.Contains(selectedDevice.DeveloperIP);
if (isReversedIp)
{
bool continueExecution = await _dialogService.ShowConfirmationAsync(
"IP Reversed",
"DeveloperIPReversed".Localized(),
"keyContinue".Localized(),
"keyStop".Localized());
if (!continueExecution)
return false;
ipMismatch = false;
}
}
if (ipMismatch)
{
bool continueExecution = await _dialogService.ShowConfirmationAsync("IP Mismatch", "DeveloperIPMismatch".Localized(), "keyContinue".Localized(), "keyStop".Localized());
if (!continueExecution)
return false;
}
@@ -94,17 +126,27 @@ namespace Jellyfin2SamsungCrossOS.Helpers
var result = await _tizenInstaller.InstallPackageAsync(
packagePath,
selectedDevice.IpAddress,
progress);
cancellationToken,
progress,
onSamsungLoginStarted);
if (result.Success)
{
var win = App.Services.GetRequiredService<InstallationCompleteWindow>();
if (Avalonia.Application.Current?.ApplicationLifetime is
Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktop)
var prettyName = GetPrettyPackageName(packagePath);
if (win.DataContext is InstallationCompleteViewModel vm)
{
win.ShowDialog(desktop.MainWindow);
vm.InstalledPackageName = prettyName;
}
if (Avalonia.Application.Current?.ApplicationLifetime is
IClassicDesktopStyleApplicationLifetime desktop)
{
await win.ShowDialog(desktop.MainWindow);
}
return true;
}
else
@@ -117,15 +159,14 @@ namespace Jellyfin2SamsungCrossOS.Helpers
catch (Exception ex)
{
progress?.Invoke("InstallationFailed".Localized());
await _dialogService.ShowErrorAsync($"{"InstallationFailed".Localized()}: {ex.Message}");
await _dialogService.ShowErrorAsync($"{"InstallationFailed".Localized()}: {ex}");
return false;
}
}
public async Task<bool> InstallCustomPackagesAsync(
string[] packagePaths,
NetworkDevice device,
Action<string> onProgress)
public async Task<bool> InstallCustomPackagesAsync(string[] packagePaths, NetworkDevice? device, CancellationToken cancellationToken, Action<string> onProgress, Action? onSamsungLoginStarted = null)
{
if (device == null) return false;
onProgress("UsingCustomWGT".Localized());
var allSuccessful = true;
@@ -140,7 +181,7 @@ namespace Jellyfin2SamsungCrossOS.Helpers
break;
}
var success = await InstallPackageAsync(filePath, device);
var success = await InstallPackageAsync(filePath, device, cancellationToken);
if (!success)
{
allSuccessful = false;
@@ -161,5 +202,15 @@ namespace Jellyfin2SamsungCrossOS.Helpers
}
catch { /* Ignore cleanup errors */ }
}
private static string GetPrettyPackageName(string packagePath)
{
var name = Path.GetFileNameWithoutExtension(packagePath);
if (string.IsNullOrEmpty(name))
return string.Empty;
return char.ToUpper(name[0]) + name.Substring(1);
}
}
}

View File

@@ -0,0 +1,46 @@
using System;
using System.IO;
using System.IO.Compression;
namespace Jellyfin2Samsung.Helpers.Core
{
public sealed class PackageWorkspace : IDisposable
{
public string Root { get; }
private readonly string _originalPackage;
private readonly string _tempPackage;
private PackageWorkspace(string root, string original, string temp)
{
Root = root;
_originalPackage = original;
_tempPackage = temp;
}
public static PackageWorkspace Extract(string packagePath)
{
var baseDir = Path.GetDirectoryName(packagePath)!;
var tempDir = Path.Combine(baseDir, $"JellyTemp_{Guid.NewGuid():N}");
Directory.CreateDirectory(tempDir);
ZipFile.ExtractToDirectory(packagePath, tempDir);
return new PackageWorkspace(tempDir, packagePath, packagePath + ".tmp");
}
public void Repack()
{
if (File.Exists(_tempPackage))
File.Delete(_tempPackage);
ZipFile.CreateFromDirectory(Root, _tempPackage);
File.Delete(_originalPackage);
File.Move(_tempPackage, _originalPackage);
}
public void Dispose()
{
try { if (Directory.Exists(Root)) Directory.Delete(Root, true); } catch { }
try { if (File.Exists(_tempPackage)) File.Delete(_tempPackage); } catch { }
}
}
}

View File

@@ -0,0 +1,195 @@
using System;
using System.IO;
using System.Security.Cryptography.X509Certificates;
namespace Jellyfin2Samsung.Helpers.Core
{
/// <summary>
/// Provides centralized platform detection and platform-specific operations.
/// Eliminates duplicate OperatingSystem.* and RuntimeInformation.* checks throughout the codebase.
/// </summary>
public static class PlatformService
{
/// <summary>
/// Represents the current operating system platform.
/// </summary>
public enum Platform
{
Windows,
Linux,
MacOS,
Unknown
}
/// <summary>
/// Gets the current platform.
/// </summary>
public static Platform CurrentPlatform
{
get
{
if (OperatingSystem.IsWindows())
return Platform.Windows;
if (OperatingSystem.IsLinux())
return Platform.Linux;
if (OperatingSystem.IsMacOS())
return Platform.MacOS;
return Platform.Unknown;
}
}
/// <summary>
/// Returns true if the current platform is Windows.
/// </summary>
public static bool IsWindows => OperatingSystem.IsWindows();
/// <summary>
/// Returns true if the current platform is Linux.
/// </summary>
public static bool IsLinux => OperatingSystem.IsLinux();
/// <summary>
/// Returns true if the current platform is macOS.
/// </summary>
public static bool IsMacOS => OperatingSystem.IsMacOS();
/// <summary>
/// Returns true if the current platform is Unix-like (Linux or macOS).
/// </summary>
public static bool IsUnixLike => IsLinux || IsMacOS;
/// <summary>
/// Gets the appropriate TizenSdb search pattern for the current platform.
/// </summary>
/// <returns>The file search pattern for TizenSdb binary.</returns>
/// <exception cref="PlatformNotSupportedException">Thrown when the platform is not supported.</exception>
public static string GetTizenSdbSearchPattern()
{
return CurrentPlatform switch
{
Platform.Windows => Constants.PlatformBinaries.TizenSdbWindowsPattern,
Platform.Linux => Constants.PlatformBinaries.TizenSdbLinuxPattern,
Platform.MacOS => Constants.PlatformBinaries.TizenSdbMacOsPattern,
_ => throw new PlatformNotSupportedException("Unsupported operating system")
};
}
/// <summary>
/// Gets the TizenSdb file name with version for the current platform.
/// </summary>
/// <param name="version">The version string to include in the file name.</param>
/// <returns>The platform-specific file name.</returns>
/// <exception cref="PlatformNotSupportedException">Thrown when the platform is not supported.</exception>
public static string GetTizenSdbFileName(string version)
{
return CurrentPlatform switch
{
Platform.Windows => $"TizenSdb_{version}{Constants.PlatformBinaries.WindowsExtension}",
Platform.Linux => $"TizenSdb_{version}{Constants.PlatformBinaries.LinuxSuffix}",
Platform.MacOS => $"TizenSdb_{version}{Constants.PlatformBinaries.MacOsSuffix}",
_ => throw new PlatformNotSupportedException("Unsupported operating system")
};
}
/// <summary>
/// Gets the platform identifier for matching GitHub release assets.
/// </summary>
/// <returns>The platform identifier string used in asset file names.</returns>
/// <exception cref="PlatformNotSupportedException">Thrown when the platform is not supported.</exception>
public static string GetAssetPlatformIdentifier()
{
return CurrentPlatform switch
{
Platform.Windows => "exe",
Platform.Linux => "linux",
Platform.MacOS => "macos",
_ => throw new PlatformNotSupportedException("Unsupported operating system")
};
}
/// <summary>
/// Gets the esbuild binary path relative to the esbuild directory.
/// </summary>
/// <param name="esbuildBasePath">The base path to the esbuild directory.</param>
/// <returns>The full path to the esbuild binary, or null if not supported.</returns>
public static string? GetEsbuildPath(string esbuildBasePath)
{
string relativePath = CurrentPlatform switch
{
Platform.Windows => Path.Combine(
Constants.PlatformBinaries.EsbuildWindows,
Constants.PlatformBinaries.EsbuildExecutableWindows),
Platform.Linux => Path.Combine(
Constants.PlatformBinaries.EsbuildLinux,
Constants.PlatformBinaries.EsbuildExecutable),
Platform.MacOS => Path.Combine(
Constants.PlatformBinaries.EsbuildMacOs,
Constants.PlatformBinaries.EsbuildExecutable),
_ => null
};
if (relativePath == null)
return null;
string fullPath = Path.Combine(esbuildBasePath, relativePath);
return File.Exists(fullPath) ? fullPath : null;
}
/// <summary>
/// Gets the ARP command arguments for the current platform.
/// </summary>
/// <param name="ipAddress">The IP address to query.</param>
/// <returns>The ARP command arguments.</returns>
public static string GetArpArguments(string ipAddress)
{
return IsWindows ? $"-a {ipAddress}" : $"-n {ipAddress}";
}
/// <summary>
/// Gets the appropriate X509KeyStorageFlags for the current platform.
/// </summary>
/// <returns>The platform-appropriate key storage flags.</returns>
public static X509KeyStorageFlags GetX509KeyStorageFlags()
{
return IsWindows
? X509KeyStorageFlags.EphemeralKeySet
: X509KeyStorageFlags.PersistKeySet;
}
/// <summary>
/// Gets firewall configuration help text for the current platform.
/// </summary>
/// <param name="port">The port number to include in instructions.</param>
/// <returns>Platform-specific firewall configuration instructions.</returns>
public static string GetFirewallHelpText(int port)
{
return CurrentPlatform switch
{
Platform.Windows =>
$"Windows:\n" +
$" netstat -ano | findstr {port}\n\n" +
$" New-NetFirewallRule -DisplayName \"Jellyfin2Samsung Logs\" \\\n" +
$" -Direction Inbound -Protocol TCP -LocalPort {port} -Action Allow\n",
Platform.Linux =>
$"Linux:\n sudo ufw allow {port}/tcp\n sudo ufw reload\n",
Platform.MacOS =>
"macOS:\n" +
" System Settings -> Network -> Firewall -> Options\n" +
" Allow incoming connections for Jellyfin2Samsung\n",
_ => "Ensure your firewall allows inbound TCP connections.\n"
};
}
/// <summary>
/// Determines if the file needs executable permissions set (Unix-like systems only).
/// </summary>
/// <returns>True if chmod is needed, false otherwise.</returns>
public static bool RequiresExecutablePermissions()
{
return IsUnixLike;
}
}
}

View File

@@ -0,0 +1,141 @@
using Jellyfin2Samsung.Models;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Jellyfin2Samsung.Helpers.Core
{
public class ProcessHelper
{
public static void KillSdbServers()
{
try
{
Process[] sdbProcesses = Process.GetProcessesByName("sdb");
if (sdbProcesses.Length == 0)
return;
foreach (Process proc in sdbProcesses)
{
proc.Kill();
proc.WaitForExit();
Trace.WriteLine($"Killed SDB {proc.Id} - {proc.ProcessName}");
}
}
catch (Exception ex)
{
Trace.WriteLine($"Failed to stop SDB server: {ex}");
}
}
string GetFirstArguments(string arguments)
{
if (string.IsNullOrWhiteSpace(arguments))
return string.Empty;
var matches = RegexPatterns.CommandLine.Arguments.Matches(arguments);
var firstTwo = matches.Cast<System.Text.RegularExpressions.Match>()
.Take(1)
.Select(m => m.Value.Trim('"'))
.ToArray();
return string.Join(" ", firstTwo);
}
public async Task<ProcessResult> RunCommandAsync(string fileName, string arguments, string? workingDirectory = null)
{
var result = new ProcessResult();
// Always run hidden (no console)
var startInfo = new ProcessStartInfo
{
FileName = fileName,
Arguments = arguments,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true,
WorkingDirectory = workingDirectory ?? ""
};
// Build log file path (next to app .exe)
string exeDir = AppContext.BaseDirectory;
string timestamp = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss-fff");
string firstTwoArgs = GetFirstArguments(arguments);
string sanitizedArguments = new(firstTwoArgs.Where(c => char.IsLetterOrDigit(c) || c == '_' || c == '-').ToArray());
if (string.IsNullOrEmpty(sanitizedArguments))
sanitizedArguments = "unknown";
string logFolder = Path.Combine(exeDir, "Logs");
string logFilePath = Path.Combine(logFolder, $"process_{sanitizedArguments}_{timestamp}.log");
try
{
using var process = new Process { StartInfo = startInfo };
process.Start();
// Capture all output
string stdOut = await process.StandardOutput.ReadToEndAsync();
string stdErr = await process.StandardError.ReadToEndAsync();
await process.WaitForExitAsync();
// Combine both
var sb = new StringBuilder();
if (!string.IsNullOrEmpty(stdOut))
sb.AppendLine(stdOut.TrimEnd());
if (!string.IsNullOrEmpty(stdErr))
{
sb.AppendLine();
sb.AppendLine("--- STDERR ---");
sb.AppendLine(stdErr.TrimEnd());
}
result.ExitCode = process.ExitCode;
result.Output = sb.ToString();
// Always write everything to a log file
try
{
Directory.CreateDirectory(exeDir);
await File.WriteAllTextAsync(logFilePath, result.Output);
result.Output += $"\n[Log written to: {logFilePath}]";
}
catch (Exception ex)
{
result.Output += $"\n[Log write failed: {ex}]";
}
}
catch (Exception ex)
{
result.ExitCode = -1;
result.Output = $"[Process start failed: {ex}]";
}
return result;
}
public async Task MakeExecutableAsync(string filePath)
{
if (PlatformService.RequiresExecutablePermissions())
{
try
{
// Use chmod to make the file executable
var output = await RunCommandAsync("chmod", $"+x \"{filePath}\"");
Trace.WriteLine($"Set executable permissions on {filePath}");
}
catch (Exception ex)
{
Trace.WriteLine($"Error setting executable permissions: {ex}");
throw;
}
}
}
}
}

View File

@@ -0,0 +1,416 @@
using System.Text.RegularExpressions;
namespace Jellyfin2Samsung.Helpers.Core
{
/// <summary>
/// Centralized regex patterns used throughout the application.
/// Pre-compiled patterns improve performance for frequently used expressions.
/// </summary>
public static partial class RegexPatterns
{
/// <summary>
/// Patterns for version parsing and extraction.
/// </summary>
public static class Version
{
/// <summary>
/// Pattern to extract version from a file name (e.g., "TizenSdb_v1.0.0.exe" -> "v1.0.0").
/// </summary>
public const string FileNameVersionPattern = @"_([v]?\d+\.\d+\.\d+)";
/// <summary>
/// Pre-compiled regex for file name version extraction.
/// </summary>
public static readonly Regex FileNameVersion = new(FileNameVersionPattern, RegexOptions.Compiled);
}
/// <summary>
/// Patterns for Tizen device capability parsing.
/// </summary>
public static class TizenCapability
{
/// <summary>
/// Pattern to extract platform version from Tizen capability output.
/// </summary>
public const string PlatformVersionPattern = @"platform_version:\s*([\d.]+)";
/// <summary>
/// Pattern to extract SDK tool path from Tizen capability output.
/// </summary>
public const string SdkToolPathPattern = @"sdk_toolpath:\s*([^\r\n]+)";
/// <summary>
/// Pattern to detect app uninstall test failure in Tizen diagnose output.
/// </summary>
public const string AppUninstallFailedPattern = @"Testing '0 vd_appuninstall test':\s*FAILED";
/// <summary>
/// Pre-compiled regex for platform version extraction.
/// </summary>
public static readonly Regex PlatformVersion = new(PlatformVersionPattern, RegexOptions.Compiled);
/// <summary>
/// Pre-compiled regex for SDK tool path extraction.
/// </summary>
public static readonly Regex SdkToolPath = new(SdkToolPathPattern, RegexOptions.Compiled);
/// <summary>
/// Pre-compiled regex for app uninstall test failure detection.
/// </summary>
public static readonly Regex AppUninstallFailed = new(
AppUninstallFailedPattern,
RegexOptions.Compiled | RegexOptions.IgnoreCase);
}
/// <summary>
/// Patterns for Tizen app information parsing.
/// </summary>
public static class TizenApp
{
/// <summary>
/// Pattern template to extract app block by title from Tizen apps output.
/// Use string.Format or interpolation with the app title.
/// </summary>
public const string AppBlockByTitleTemplate = @"(^\s*-+app_title\s*=\s*{0}.*?)(?=^\s*-+app_title|\Z)";
/// <summary>
/// Pattern to extract Tizen app ID from an app block.
/// </summary>
public const string AppTizenIdPattern = @"app_tizen_id\s*=\s*([A-Za-z0-9._]+)";
/// <summary>
/// Pattern to extract Tizen app ID with delimiter.
/// </summary>
public const string AppTizenIdWithDelimiterPattern = @"app_tizen_id\s*=\s*([A-Za-z0-9._-]+?)(?=-{3,})";
/// <summary>
/// Pre-compiled regex for app Tizen ID extraction.
/// </summary>
public static readonly Regex AppTizenId = new(
AppTizenIdPattern,
RegexOptions.Compiled | RegexOptions.IgnoreCase);
/// <summary>
/// Pre-compiled regex for app Tizen ID with delimiter extraction.
/// </summary>
public static readonly Regex AppTizenIdWithDelimiter = new(
AppTizenIdWithDelimiterPattern,
RegexOptions.Compiled | RegexOptions.IgnoreCase);
/// <summary>
/// Creates a regex to find an app block by title.
/// </summary>
/// <param name="appTitle">The app title to search for.</param>
/// <returns>A regex instance for matching the app block.</returns>
public static Regex CreateAppBlockByTitleRegex(string appTitle)
{
var escapedTitle = Regex.Escape(appTitle);
var pattern = string.Format(AppBlockByTitleTemplate, escapedTitle);
return new Regex(
pattern,
RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Multiline);
}
}
/// <summary>
/// Patterns for WGT package config.xml parsing.
/// </summary>
public static class WgtConfig
{
/// <summary>
/// Pattern to extract package ID from Tizen application element in config.xml.
/// </summary>
public const string TizenApplicationIdPattern =
@"<tizen:application\s+id=""(?<pkg>[A-Za-z0-9]+)\.Jellyfin""\s+package=""\k<pkg>""";
/// <summary>
/// Pre-compiled regex for Tizen application ID extraction.
/// </summary>
public static readonly Regex TizenApplicationId = new(
TizenApplicationIdPattern,
RegexOptions.Compiled | RegexOptions.Multiline);
/// <summary>
/// Creates a pattern to match and replace a specific package ID in config.xml.
/// </summary>
/// <param name="packageId">The package ID to match.</param>
/// <returns>The pattern string.</returns>
public static string CreatePackageIdReplacePattern(string packageId)
{
return $@"<tizen:application\s+id=""{packageId}\.Jellyfin""\s+package=""{packageId}""";
}
}
/// <summary>
/// Patterns for HTML parsing and manipulation.
/// </summary>
public static class Html
{
/// <summary>
/// Pattern to extract href from link elements.
/// </summary>
public const string LinkHrefPattern = @"<link[^>]+href=[""']([^""']+)[""'][^>]*>";
/// <summary>
/// Pattern to extract src from script elements.
/// </summary>
public const string ScriptSrcPattern = @"<script[^>]+src=[""']([^""']+)[""'][^>]*>[\s\S]*?<\/script>";
/// <summary>
/// Pattern to match base tag elements for replacement.
/// </summary>
public const string BaseTagPattern = @"<base[^>]+>";
/// <summary>
/// Pattern to rewrite local paths (src/href with /web/ prefix).
/// </summary>
public const string LocalPathsPattern = @"(src|href)=""[^""]*/web/([^""]+)""";
/// <summary>
/// Pattern to match Content-Security-Policy meta tags.
/// </summary>
public const string CspMetaPattern = @"<meta[^>]*Content-Security-Policy[^>]*>";
/// <summary>
/// Pre-compiled regex for link href extraction.
/// </summary>
public static readonly Regex LinkHref = new(
LinkHrefPattern,
RegexOptions.Compiled | RegexOptions.IgnoreCase);
/// <summary>
/// Pre-compiled regex for script src extraction.
/// </summary>
public static readonly Regex ScriptSrc = new(
ScriptSrcPattern,
RegexOptions.Compiled | RegexOptions.IgnoreCase);
/// <summary>
/// Pre-compiled regex for base tag matching.
/// </summary>
public static readonly Regex BaseTag = new(
BaseTagPattern,
RegexOptions.Compiled | RegexOptions.IgnoreCase);
/// <summary>
/// Pre-compiled regex for local paths rewriting.
/// </summary>
public static readonly Regex LocalPaths = new(
LocalPathsPattern,
RegexOptions.Compiled);
/// <summary>
/// Pre-compiled regex for CSP meta tag matching.
/// </summary>
public static readonly Regex CspMeta = new(
CspMetaPattern,
RegexOptions.Compiled | RegexOptions.IgnoreCase);
}
/// <summary>
/// Patterns for network and MAC address parsing.
/// </summary>
public static class Network
{
/// <summary>
/// Pattern to extract MAC address from ARP output.
/// </summary>
public const string MacAddressPattern = @"([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})";
/// <summary>
/// Pre-compiled regex for MAC address extraction.
/// </summary>
public static readonly Regex MacAddress = new(MacAddressPattern, RegexOptions.Compiled);
}
/// <summary>
/// Patterns for command line argument parsing.
/// </summary>
public static class CommandLine
{
/// <summary>
/// Pattern to parse command line arguments (handles quoted strings).
/// </summary>
public const string ArgumentsPattern = @"[\""].+?[\""]|[^ ]+";
/// <summary>
/// Pre-compiled regex for argument parsing.
/// </summary>
public static readonly Regex Arguments = new(ArgumentsPattern, RegexOptions.Compiled);
}
/// <summary>
/// Patterns for build info markdown parsing.
/// </summary>
public static class BuildInfo
{
/// <summary>
/// Pattern to extract versions table from markdown.
/// </summary>
public const string VersionsTablePattern = @"## Versions\s*\n(?<table>(\|[^\n]+\n)+)";
/// <summary>
/// Pattern to extract applications table from markdown.
/// </summary>
public const string ApplicationsTablePattern =
@"\|\s*🧩 Application\s*\|\s*📝 Description\s*\|\s*🔗 Repository\s*\|\s*\n(?<table>(\|[^\n]+\n)+)";
/// <summary>
/// Pattern to extract table rows with 2 columns.
/// </summary>
public const string TableRow2ColumnsPattern = @"^\|([^|]+)\|([^|]+)\|";
/// <summary>
/// Pattern to extract table rows with 3 columns.
/// </summary>
public const string TableRow3ColumnsPattern = @"^\|([^|]+)\|([^|]+)\|([^|]+)\|";
/// <summary>
/// Pattern to remove markdown bold formatting.
/// </summary>
public const string MarkdownBoldPattern = @"\*\*(.*?)\*\*";
/// <summary>
/// Pattern to remove emoji characters.
/// </summary>
public const string EmojiRangePattern = @"[\u2600-\u27BF]";
/// <summary>
/// Pre-compiled regex for versions table extraction.
/// </summary>
public static readonly Regex VersionsTable = new(
VersionsTablePattern,
RegexOptions.Compiled | RegexOptions.Multiline);
/// <summary>
/// Pre-compiled regex for applications table extraction.
/// </summary>
public static readonly Regex ApplicationsTable = new(
ApplicationsTablePattern,
RegexOptions.Compiled | RegexOptions.Multiline);
/// <summary>
/// Pre-compiled regex for 2-column table rows.
/// </summary>
public static readonly Regex TableRow2Columns = new(
TableRow2ColumnsPattern,
RegexOptions.Compiled | RegexOptions.Multiline);
/// <summary>
/// Pre-compiled regex for 3-column table rows.
/// </summary>
public static readonly Regex TableRow3Columns = new(
TableRow3ColumnsPattern,
RegexOptions.Compiled | RegexOptions.Multiline);
/// <summary>
/// Pre-compiled regex for markdown bold removal.
/// </summary>
public static readonly Regex MarkdownBold = new(MarkdownBoldPattern, RegexOptions.Compiled);
/// <summary>
/// Pre-compiled regex for emoji removal.
/// </summary>
public static readonly Regex EmojiRange = new(EmojiRangePattern, RegexOptions.Compiled);
}
/// <summary>
/// Patterns for plugin configuration parsing.
/// </summary>
public static class PluginConfig
{
/// <summary>
/// Pattern to extract defaultSkin value from JSON config.
/// </summary>
public const string DefaultSkinPattern = @"""defaultSkin""\s*:\s*""([^""]+)""";
/// <summary>
/// Pre-compiled regex for defaultSkin extraction.
/// </summary>
public static readonly Regex DefaultSkin = new(
DefaultSkinPattern,
RegexOptions.Compiled | RegexOptions.IgnoreCase);
}
/// <summary>
/// Patterns for extension parsing in Tizen package manager output.
/// </summary>
public static class Extension
{
/// <summary>
/// Pattern to parse extension entries from package manager output.
/// </summary>
public const string ExtensionEntryPattern =
@"Index\s*:\s*(\d+)\s+Name\s*:\s*(.*?)\s+Repository\s*:\s*.*?\s+Id\s*:\s*.*?\s+Vendor\s*:\s*.*?\s+Description\s*:\s*.*?\s+Default\s*:\s*.*?\s+Activate\s*:\s*(true|false)";
/// <summary>
/// Pre-compiled regex for extension entry parsing.
/// </summary>
public static readonly Regex ExtensionEntry = new(
ExtensionEntryPattern,
RegexOptions.Compiled | RegexOptions.Singleline);
}
/// <summary>
/// Patterns for KefinTweaks plugin patching.
/// </summary>
public static class KefinTweaks
{
/// <summary>
/// Pattern to detect KefinTweaks loader script reference in public.js.
/// </summary>
public const string LoaderPattern =
@"script\.src\s*=\s*['""]https:\/\/cdn\.jsdelivr\.net\/gh\/ranaldsgift\/KefinTweaks[^'""]+['""]";
/// <summary>
/// Pattern to match and replace kefinTweaksRoot configuration.
/// </summary>
public const string TweaksRootPattern =
@"""kefinTweaksRoot""\s*:\s*""https:\/\/cdn\.jsdelivr\.net\/gh\/ranaldsgift\/KefinTweaks@latest\/""";
/// <summary>
/// Pattern to extract script entries from injector.js.
/// </summary>
public const string ScriptEntryPattern = @"script\s*:\s*""([^""]+)""";
/// <summary>
/// Pre-compiled regex for loader detection.
/// </summary>
public static readonly Regex Loader = new(
LoaderPattern,
RegexOptions.Compiled | RegexOptions.IgnoreCase);
/// <summary>
/// Pre-compiled regex for tweaks root replacement.
/// </summary>
public static readonly Regex TweaksRoot = new(
TweaksRootPattern,
RegexOptions.Compiled | RegexOptions.IgnoreCase);
/// <summary>
/// Pre-compiled regex for script entry extraction.
/// </summary>
public static readonly Regex ScriptEntry = new(
ScriptEntryPattern,
RegexOptions.Compiled);
}
/// <summary>
/// Patterns for plugin name cleaning and normalization.
/// </summary>
public static class PluginName
{
/// <summary>
/// Pattern to remove non-alphanumeric characters from plugin names.
/// </summary>
public const string NonAlphanumericPattern = @"[^a-z0-9]";
/// <summary>
/// Pre-compiled regex for plugin name cleaning.
/// </summary>
public static readonly Regex NonAlphanumeric = new(
NonAlphanumericPattern,
RegexOptions.Compiled);
}
}
}

View File

@@ -0,0 +1,94 @@
using System;
namespace Jellyfin2Samsung.Helpers.Core
{
/// <summary>
/// Provides URL manipulation utilities.
/// Centralizes URL normalization to eliminate duplicate TrimEnd('/') calls across the codebase.
/// </summary>
public static class UrlHelper
{
/// <summary>
/// Normalizes a server URL by removing trailing slashes.
/// </summary>
/// <param name="url">The URL to normalize.</param>
/// <returns>The normalized URL without trailing slashes, or empty string if null.</returns>
public static string NormalizeServerUrl(string? url)
{
if (string.IsNullOrWhiteSpace(url))
return string.Empty;
return url.TrimEnd('/');
}
/// <summary>
/// Combines a base URL with a path segment, ensuring proper slash handling.
/// </summary>
/// <param name="baseUrl">The base URL (trailing slash will be removed).</param>
/// <param name="path">The path to append (leading slash will be added if missing).</param>
/// <returns>The combined URL.</returns>
public static string CombineUrl(string? baseUrl, string? path)
{
var normalizedBase = NormalizeServerUrl(baseUrl);
if (string.IsNullOrWhiteSpace(path))
return normalizedBase;
var normalizedPath = path.TrimStart('/');
return string.IsNullOrEmpty(normalizedBase)
? normalizedPath
: $"{normalizedBase}/{normalizedPath}";
}
/// <summary>
/// Creates an absolute URI from a base server URL and a relative or absolute path.
/// </summary>
/// <param name="serverUrl">The base server URL.</param>
/// <param name="relativeOrAbsolutePath">A relative path or absolute URL.</param>
/// <returns>The absolute URI.</returns>
public static Uri GetAbsoluteUri(string serverUrl, string relativeOrAbsolutePath)
{
if (Uri.IsWellFormedUriString(relativeOrAbsolutePath, UriKind.Absolute))
return new Uri(relativeOrAbsolutePath);
var baseUri = new Uri(NormalizeServerUrl(serverUrl) + "/");
return new Uri(baseUri, relativeOrAbsolutePath.TrimStart('/'));
}
/// <summary>
/// Validates if a string is a valid HTTP or HTTPS URL.
/// </summary>
/// <param name="url">The URL to validate.</param>
/// <returns>True if the URL is valid, false otherwise.</returns>
public static bool IsValidHttpUrl(string? url)
{
if (string.IsNullOrWhiteSpace(url))
return false;
return Uri.TryCreate(url, UriKind.Absolute, out var uriResult)
&& (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps);
}
/// <summary>
/// Extracts the file name from a URL path.
/// </summary>
/// <param name="url">The URL to extract the file name from.</param>
/// <returns>The file name, or empty string if not found.</returns>
public static string GetFileNameFromUrl(string url)
{
if (string.IsNullOrWhiteSpace(url))
return string.Empty;
try
{
var uri = new Uri(url);
return System.IO.Path.GetFileName(uri.LocalPath);
}
catch
{
return string.Empty;
}
}
}
}

View File

@@ -1,104 +0,0 @@
using Jellyfin2SamsungCrossOS.Models;
using Jellyfin2SamsungCrossOS.Services;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace Jellyfin2SamsungCrossOS.Helpers
{
public class DeviceHelper
{
private readonly INetworkService _networkService;
private readonly IDialogService _dialogService;
private readonly HttpClient _httpClient;
public DeviceHelper(
INetworkService networkService,
IDialogService dialogService,
HttpClient httpClient)
{
_networkService = networkService;
_dialogService = dialogService;
_httpClient = httpClient;
}
public async Task<NetworkDevice> GetDeveloperInfoAsync(NetworkDevice device)
{
try
{
string url = $"http://{device.IpAddress}:8001/api/v2/";
var response = await _httpClient.GetAsync(url);
response.EnsureSuccessStatusCode();
string jsonContent = await response.Content.ReadAsStringAsync();
JObject jsonObject = JObject.Parse(jsonContent);
return new NetworkDevice
{
IpAddress = jsonObject["device"]?["ip"]?.ToString(),
DeviceName = WebUtility.HtmlDecode(jsonObject["device"]?["name"]?.ToString()),
ModelName = jsonObject["device"]?["modelName"].ToString(),
Manufacturer = jsonObject["device"]?["type"]?.ToString(),
DeveloperMode = jsonObject["device"]?["developerMode"]?.ToString() ?? string.Empty,
DeveloperIP = jsonObject["device"]?["developerIP"]?.ToString() ?? string.Empty
};
}
catch (HttpRequestException ex)
{
await _dialogService.ShowErrorAsync(
$"Error connecting to Samsung TV at {device.IpAddress}: {ex.Message}");
}
catch (JsonException ex)
{
await _dialogService.ShowErrorAsync(
$"Error parsing JSON response: {ex.Message}");
}
catch (Exception ex)
{
await _dialogService.ShowErrorAsync(
$"Unexpected error: {ex.Message}");
}
return new NetworkDevice
{
IpAddress = device.IpAddress,
DeviceName = device.DeviceName,
Manufacturer = device.Manufacturer,
DeveloperMode = string.Empty,
DeveloperIP = string.Empty
};
}
public async Task<List<NetworkDevice>> ScanForDevicesAsync(CancellationToken cancellationToken = default, bool virtualScan = false)
{
var devices = new List<NetworkDevice>();
var networkDevices = await _networkService.GetLocalTizenAddresses(cancellationToken, virtualScan);
foreach (NetworkDevice device in networkDevices)
{
if (await _networkService.IsPortOpenAsync(device.IpAddress, 8001, cancellationToken))
{
try
{
var samsungDevice = await GetDeveloperInfoAsync(device);
if (!string.IsNullOrEmpty(samsungDevice.DeviceName))
devices.Add(samsungDevice);
}
catch
{
}
}
}
return devices;
}
}
}

View File

@@ -1,94 +0,0 @@
using Avalonia.Platform.Storage;
using Jellyfin2SamsungCrossOS.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Jellyfin2SamsungCrossOS.Helpers
{
public class FileHelper
{
private static readonly string[] wgtItem = ["*.wgt"];
private static readonly string[] tpkItem = ["*.tpk"];
private static readonly string[] allItem = ["*.wgt", "*.tpk"];
public async Task<string?> BrowseWgtFilesAsync(IStorageProvider storageProvider)
{
var fileTypes = new List<FilePickerFileType>
{
new("WGT Files")
{
Patterns = wgtItem
},
new("TPK Files")
{
Patterns = tpkItem
},
new("All Supported Files")
{
Patterns = allItem
}
};
var options = new FilePickerOpenOptions
{
Title = "Select WGT/TPK File",
FileTypeFilter = fileTypes,
AllowMultiple = true
};
var files = await storageProvider.OpenFilePickerAsync(options);
if (files?.Any() == true)
{
var newPaths = new List<string>();
foreach (var file in files)
{
var originalPath = file.Path.LocalPath;
var directory = Path.GetDirectoryName(originalPath);
var baseName = Path.GetFileNameWithoutExtension(originalPath);
var extension = Path.GetExtension(originalPath);
var random = new Random();
const string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
var randomSuffix = new string(Enumerable.Range(0, 4).Select(_ => chars[random.Next(chars.Length)]).ToArray());
var newFileName = $"{baseName}{randomSuffix}{extension}";
var newFilePath = Path.Combine(directory ?? Environment.CurrentDirectory, newFileName);
File.Copy(originalPath, newFilePath, overwrite: true);
newPaths.Add(newFilePath);
}
return string.Join(";", newPaths);
}
return null;
}
public List<ExtensionEntry> ParseExtensions(string output)
{
var extensions = new List<ExtensionEntry>();
var regex = new Regex(
@"Index\s*:\s*(\d+)\s+Name\s*:\s*(.*?)\s+Repository\s*:\s*.*?\s+Id\s*:\s*.*?\s+Vendor\s*:\s*.*?\s+Description\s*:\s*.*?\s+Default\s*:\s*.*?\s+Activate\s*:\s*(true|false)",
RegexOptions.Singleline);
foreach (Match match in regex.Matches(output))
{
extensions.Add(new ExtensionEntry
{
Index = int.Parse(match.Groups[1].Value),
Name = match.Groups[2].Value.Trim(),
Activated = bool.Parse(match.Groups[3].Value)
});
}
return extensions;
}
}
}

View File

@@ -0,0 +1,46 @@
using Jellyfin2Samsung.Helpers.Core;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading.Tasks;
namespace Jellyfin2Samsung.Helpers.Jellyfin.CSS
{
/// <summary>
/// Injects custom CSS into the Jellyfin web app.
/// Supports both inline CSS and @import rules for external themes like ElegantFin or Ultrachromic.
/// </summary>
public class CustomCss
{
public async Task InjectAsync(PackageWorkspace ws)
{
var customCss = AppSettings.Default.CustomCss;
if (string.IsNullOrWhiteSpace(customCss))
{
Trace.WriteLine("[InjectCustomCss] No custom CSS configured, skipping injection");
return;
}
string indexPath = Path.Combine(ws.Root, "www", "index.html");
if (!File.Exists(indexPath))
{
Trace.WriteLine("[InjectCustomCss] index.html not found");
return;
}
var html = await File.ReadAllTextAsync(indexPath);
var cssBlock = new StringBuilder();
cssBlock.AppendLine("<style id=\"jellyfin-custom-css\">");
cssBlock.AppendLine(customCss);
cssBlock.AppendLine("</style>");
// Inject before </head> to ensure CSS is loaded with the page
html = html.Replace("</head>", cssBlock + "</head>");
await File.WriteAllTextAsync(indexPath, html);
Trace.WriteLine("[InjectCustomCss] Custom CSS injected successfully");
}
}
}

View File

@@ -0,0 +1,56 @@
using Jellyfin2Samsung.Helpers.Core;
using System.IO;
using System.Text;
using System.Threading.Tasks;
namespace Jellyfin2Samsung.Helpers.Jellyfin.Diagnostic
{
public class JellyfinDiagnostic
{
public async Task InjectDevLogsAsync(PackageWorkspace ws)
{
string indexPath = Path.Combine(ws.Root, "www", "index.html");
if (!File.Exists(indexPath)) return;
var html = await File.ReadAllTextAsync(indexPath);
// 2. CSP UPDATE: WebSockets (ws:) are often blocked by default CSP
if (html.Contains("Content-Security-Policy"))
{
// Add ws: to the connect-src or default-src
html = html.Replace("default-src", "connect-src * ws: wss:; default-src");
}
var script = new StringBuilder();
script.AppendLine("<script>");
script.AppendLine("(function(){");
script.AppendLine(" try {");
script.AppendLine($" var ws = new WebSocket('ws://{AppSettings.Default.LocalIp}:54321');");
script.AppendLine(" ws.onopen = function(){ ws.send('INJECTOR: websocket connected'); };");
script.AppendLine(" ws.onerror = function(e){ alert('WS Error. Check Firewall on port 54321'); };");
// ES5 Helper (No Arrow Functions)
script.AppendLine(" var s = function(t, d) {");
script.AppendLine(" try { if(ws.readyState === 1) ws.send(JSON.stringify({type:t, data:d})); } catch(e){}");
script.AppendLine(" };");
// console.log override (No Spread Operator)
script.AppendLine(" var oldLog = console.log;");
script.AppendLine(" console.log = function() {");
script.AppendLine(" var args = Array.prototype.slice.call(arguments);");
script.AppendLine(" if(oldLog) oldLog.apply(console, args);");
script.AppendLine(" s('log', args);");
script.AppendLine(" };");
script.AppendLine(" window.onerror = function(m, sr, l, c) { s('error', [m, sr, l, c]); };");
script.AppendLine(" } catch (e) { alert('Injector Failed: ' + e.message); }");
script.AppendLine("})();");
script.AppendLine("</script>");
// Inject before </head>
html = html.Replace("</head>", script.ToString() + "\n</head>");
await File.WriteAllTextAsync(indexPath, html);
}
}
}

View File

@@ -0,0 +1,77 @@
using Jellyfin2Samsung.Helpers.API;
using Jellyfin2Samsung.Helpers.Core;
using Jellyfin2Samsung.Helpers.Jellyfin.CSS;
using Jellyfin2Samsung.Helpers.Jellyfin.Diagnostic;
using Jellyfin2Samsung.Helpers.Jellyfin.Fixes;
using Jellyfin2Samsung.Helpers.Jellyfin.Patches;
using Jellyfin2Samsung.Helpers.Jellyfin.Plugins;
using Jellyfin2Samsung.Models;
using System.Diagnostics;
using System.Net.Http;
using System.Threading.Tasks;
namespace Jellyfin2Samsung.Helpers.Jellyfin
{
public class JellyfinPackagePatcher
{
private readonly JellyfinIndex _indexHtml;
private readonly JellyfinDiagnostic _diagnostic;
private readonly FixYouTube _youTube;
private readonly CustomCss _customCss;
public JellyfinPackagePatcher(HttpClient http)
{
var api = new JellyfinApiClient(http);
var plugins = new PluginManager(http, api);
_indexHtml = new JellyfinIndex(http, api, plugins);
_diagnostic = new JellyfinDiagnostic();
_youTube = new FixYouTube();
_customCss = new CustomCss();
}
public async Task<InstallResult> ApplyJellyfinConfigAsync(string packagePath)
{
using var ws = PackageWorkspace.Extract(packagePath);
// Apply server scripts (JS injection) if enabled
if (AppSettings.Default.UseServerScripts)
await _indexHtml.PatchIndexAsync(ws, AppSettings.Default.JellyfinFullUrl);
// Apply YouTube plugin patch if enabled
if (AppSettings.Default.PatchYoutubePlugin)
{
await _youTube.PatchPluginAsync(ws);
await _youTube.UpdateCorsAsync(ws);
await _youTube.CreateYouTubeResolverAsync(ws);
}
// Always update server address
await _indexHtml.UpdateServerAddressAsync(ws);
// Inject auto-login credentials if available
if (!string.IsNullOrEmpty(AppSettings.Default.JellyfinAccessToken) &&
!string.IsNullOrEmpty(AppSettings.Default.JellyfinUserId))
{
Trace.WriteLine("Injecting auto-login credentials...");
await _indexHtml.InjectAutoLoginAsync(ws);
}
if (AppSettings.Default.EnableDevLogs)
{
Trace.WriteLine("Injecting dev logs...");
await _diagnostic.InjectDevLogsAsync(ws);
}
// Inject custom CSS if configured
if (!string.IsNullOrWhiteSpace(AppSettings.Default.CustomCss))
{
Trace.WriteLine("Injecting custom CSS...");
await _customCss.InjectAsync(ws);
}
ws.Repack();
return InstallResult.SuccessResult();
}
}
}

View File

@@ -0,0 +1,935 @@
using Jellyfin2Samsung.Helpers.Core;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace Jellyfin2Samsung.Helpers.Jellyfin.Fixes
{
public class FixYouTube
{
private async Task<int> ResolveServicePortAsync(PackageWorkspace ws)
{
var packageId = await FileHelper.ReadExtractedWgtPackageId(ws.Root);
return packageId == "JepZAARz4r" ? 8124 : 8123;
}
public async Task PatchPluginAsync(PackageWorkspace ws)
{
int servicePort = await ResolveServicePortAsync(ws);
var www = Path.Combine(ws.Root, "www");
var utf8NoBom = new UTF8Encoding(false);
var candidates = Directory.GetFiles(www, "youtubePlayer-plugin*.js", SearchOption.AllDirectories)
.Concat(Directory.GetFiles(www, "youtubePlayer-plugin*.chunk.js", SearchOption.AllDirectories))
.Distinct()
.ToList();
// V17: YT object protection + trailer fallback for non-English metadata
string injected = """
(function () {
if (window.__YT_FIX_V17__) return;
window.__YT_FIX_V17__ = true;
var SERVICE_BASE = 'http://localhost:8123';
var currentPlayerInstance = null;
function sLog(msg, data) {
try {
var xhr = new XMLHttpRequest();
xhr.open('POST', SERVICE_BASE + '/log', true);
xhr.setRequestHeader('Content-Type', 'application/json');
var cleanData = (data && typeof data === 'object') ? JSON.stringify(data) : (data || '');
xhr.send(JSON.stringify({args: ['[V17]', msg, cleanData]}));
} catch(e) {}
}
sLog('LOADED', { href: window.location.href });
try {
var appId = tizen.application.getCurrentApplication().appInfo.id;
var pkgId = appId.split('.')[0];
tizen.application.launch(pkgId + '.ytresolver', function() { sLog('SVC_LAUNCH_OK'); }, function(e) { sLog('SVC_LAUNCH_ERR', e.message); });
} catch (e) { sLog('SVC_LAUNCH_FAIL', e.message); }
// ========================================================================
// CUSTOM PLAYER CLASS - Mirrors Jellyfin's YoutubePlayer interface
// ========================================================================
function CustomPlayer(idOrEl, cfg) {
var self = this;
currentPlayerInstance = this;
var videoId = '';
if (typeof cfg === 'string') videoId = cfg;
else if (cfg && typeof cfg === 'object') videoId = cfg.videoId || cfg.id || '';
this._state = -1;
this._currentTime = 0;
this._duration = 0;
this._volume = 100;
this._ready = false;
this._queue = [];
this._destroyed = false;
this._container = null;
this._iframe = null;
this._observer = null;
this._messageHandler = null;
var container = (typeof idOrEl === 'string') ? document.getElementById(idOrEl) : idOrEl;
this._container = container;
var iframe = document.createElement('iframe');
this._iframe = iframe;
// Message handler for iframe communication
this._messageHandler = function(ev) {
if (self._destroyed) return;
var m = ev.data;
if (!m || !m.__ytbridge) return;
if (m.type === 'ready') {
self._ready = true;
sLog('IFRAME_READY');
// HIDE SPINNER AGAIN (in case it reappeared)
try {
if (window.Loading && window.Loading.hide) {
window.Loading.hide();
}
var spinners = document.querySelectorAll('.docspinner');
for (var i = 0; i < spinners.length; i++) {
spinners[i].classList.remove('mdlSpinnerActive');
}
} catch(e) {}
if (cfg.events && cfg.events.onReady) cfg.events.onReady({ target: self });
// Process queued commands
while(self._queue.length) {
var q = self._queue.shift();
self._send(q.cmd, q.val);
}
// ENSURE AUTOPLAY: Jellyfin expects video to start playing immediately
// The YouTube iframe should auto-play, but we'll trigger it again to be sure
setTimeout(function() {
sLog('AUTOPLAY_TRIGGER');
self._send('play');
}, 200);
} else if (m.type === 'state') {
self._state = m.data;
if (cfg.events && cfg.events.onStateChange) {
cfg.events.onStateChange({ target: self, data: m.data });
}
} else if (m.type === 'time') {
self._currentTime = m.t / 1000;
self._duration = m.d / 1000;
self._state = m.s;
} else if (m.type === 'error') {
if (cfg.events && cfg.events.onError) {
cfg.events.onError(m.data);
}
}
};
window.addEventListener('message', this._messageHandler);
function mount() {
if (!container || self._destroyed) return;
sLog('MOUNTING', { extractedId: videoId });
if (!videoId) {
sLog('ERR_MISSING_ID', 'Missing videoId');
return;
}
// HIDE JELLYFIN LOADING SPINNER
try {
if (window.Loading && window.Loading.hide) {
window.Loading.hide();
sLog('SPINNER_HIDDEN_VIA_API');
}
// Fallback: directly remove spinner classes
var spinners = document.querySelectorAll('.docspinner');
for (var i = 0; i < spinners.length; i++) {
spinners[i].classList.remove('mdlSpinnerActive');
}
} catch(e) {
sLog('SPINNER_HIDE_ERR', e.message);
}
// Use fixed positioning with max z-index to stay on top
iframe.style.cssText = 'width:100vw; height:100vh; border:0; background:#000; position:fixed; top:0; left:0; z-index:2147483647;';
iframe.setAttribute('allow', 'autoplay; encrypted-media; fullscreen');
iframe.src = SERVICE_BASE + '/player.html?videoId=' + encodeURIComponent(videoId);
container.innerHTML = '';
container.appendChild(iframe);
// Watch for React wiping the container
self._observer = new MutationObserver(function(mutations) {
if (self._destroyed) return;
if (container && !container.contains(iframe)) {
sLog('REACT_WIPE_RESTORE');
container.appendChild(iframe);
}
});
self._observer.observe(container, { childList: true });
}
this._send = function(cmd, val) {
if (this._destroyed) return;
if (!this._ready) {
this._queue.push({cmd:cmd, val:val});
return;
}
if (this._iframe && this._iframe.contentWindow) {
this._iframe.contentWindow.postMessage({ __ytbridge_cmd: true, cmd: cmd, val: val }, '*');
}
};
// API Methods matching YT.Player interface
this.playVideo = function() {
sLog('CMD_PLAY');
this._send('play');
};
this.pauseVideo = function() {
sLog('CMD_PAUSE');
this._send('pause');
};
this.stopVideo = function() {
sLog('CMD_STOP');
this._send('stop');
};
this.seekTo = function(s, allowSeekAhead) {
sLog('CMD_SEEK', s);
this._send('seek', s * 1000);
};
this.setVolume = function(v) {
this._volume = v;
this._send('volume', v);
};
this.getVolume = function() {
return this._volume;
};
this.getCurrentTime = function() {
return this._currentTime;
};
this.getDuration = function() {
return this._duration;
};
this.getPlayerState = function() {
return this._state;
};
this.mute = function() {
this._send('mute', true);
};
this.unMute = function() {
this._send('mute', false);
};
this.isMuted = function() {
return this._muted || false;
};
this.setSize = function(width, height) {
// Size is already 100vw/100vh, no action needed
sLog('SET_SIZE', { w: width, h: height });
};
// CRITICAL: Proper cleanup to prevent background playback
this.destroy = function() {
sLog('DESTROY_CALLED');
if (this._destroyed) return;
this._destroyed = true;
// Stop playback first
if (this._iframe && this._iframe.contentWindow) {
try {
this._iframe.contentWindow.postMessage({ __ytbridge_cmd: true, cmd: 'stop' }, '*');
} catch(e) {
sLog('DESTROY_STOP_ERR', e.message);
}
}
// Clean up event listeners
if (this._messageHandler) {
window.removeEventListener('message', this._messageHandler);
this._messageHandler = null;
}
// Disconnect observer
if (this._observer) {
this._observer.disconnect();
this._observer = null;
}
// Remove iframe from DOM
if (this._iframe) {
if (this._iframe.parentNode) {
this._iframe.parentNode.removeChild(this._iframe);
}
this._iframe = null;
}
// Clear container
if (this._container) {
this._container.innerHTML = '';
this._container = null;
}
// Clear queue
this._queue = [];
if (currentPlayerInstance === this) {
currentPlayerInstance = null;
}
sLog('DESTROYED');
};
mount();
}
// ========================================================================
// YT NAMESPACE - Make it immutable to prevent overwriting
// ========================================================================
var customYT = {
Player: CustomPlayer,
PlayerState: {
UNSTARTED: -1,
ENDED: 0,
PLAYING: 1,
PAUSED: 2,
BUFFERING: 3,
CUED: 5
},
loaded: 1,
__CUSTOM__: true
};
// Make YT property non-writable so it can't be overwritten
Object.defineProperty(window, 'YT', {
value: customYT,
writable: false,
configurable: false,
enumerable: true
});
sLog('YT_PROTECTED');
// Trigger ready callback if it exists
if (window.onYouTubeIframeAPIReady) {
setTimeout(function() {
sLog('TRIGGER_READY_CALLBACK');
window.onYouTubeIframeAPIReady();
}, 100);
}
// ========================================================================
// NAVIGATION CLEANUP - Hook into Jellyfin's router (FIXED)
// ========================================================================
var lastPath = window.location.pathname;
// Listen for Jellyfin page changes
document.addEventListener('viewshow', function() {
var currentPath = window.location.pathname;
sLog('VIEW_SHOW_EVENT', { lastPath: lastPath, currentPath: currentPath });
// Only cleanup if we actually navigated away (path changed)
if (currentPath !== lastPath) {
lastPath = currentPath;
// If we're navigating away from video page, ensure cleanup
if (currentPath !== '/video' && currentPlayerInstance) {
sLog('NAV_CLEANUP_TRIGGER');
try {
currentPlayerInstance.destroy();
} catch(e) {
sLog('NAV_CLEANUP_ERR', e.message);
}
}
} else {
sLog('VIEW_SHOW_SAME_PATH');
}
});
// Also listen for back button via popstate
window.addEventListener('popstate', function() {
sLog('POPSTATE_EVENT');
setTimeout(function() {
var currentPath = window.location.pathname;
if (currentPath !== '/video' && currentPlayerInstance) {
sLog('POPSTATE_CLEANUP_TRIGGER');
try {
currentPlayerInstance.destroy();
} catch(e) {
sLog('POPSTATE_CLEANUP_ERR', e.message);
}
}
}, 100);
});
// ========================================================================
// TRAILER FALLBACK - Find trailers for non-English metadata languages
// ========================================================================
var _tfTimer = null;
var _tfTmdbKey = '';
var _tfKeyFetched = false;
// Fetch TMDB API key from Jellyfin server (works for admin users, silent fail for others)
function _tfFetchTmdbKey(api, cb) {
if (_tfKeyFetched) return cb();
_tfKeyFetched = true;
var xhr = new XMLHttpRequest();
xhr.open('GET', api.serverAddress() + '/Plugins', true);
xhr.setRequestHeader('Authorization', 'MediaBrowser Token="' + api.accessToken() + '"');
xhr.onload = function() {
if (xhr.status !== 200) { sLog('TF_KEY_NO_PLUGINS'); return cb(); }
try {
var plugins = JSON.parse(xhr.responseText);
var tmdbPlugin = null;
for (var i = 0; i < plugins.length; i++) {
if (plugins[i].Name && plugins[i].Name.indexOf('TMDb') !== -1) { tmdbPlugin = plugins[i]; break; }
}
if (!tmdbPlugin) { sLog('TF_KEY_NO_TMDB_PLUGIN'); return cb(); }
var xhr2 = new XMLHttpRequest();
xhr2.open('GET', api.serverAddress() + '/Plugins/' + tmdbPlugin.Id + '/Configuration', true);
xhr2.setRequestHeader('Authorization', 'MediaBrowser Token="' + api.accessToken() + '"');
xhr2.onload = function() {
if (xhr2.status === 200) {
try {
var cfg = JSON.parse(xhr2.responseText);
if (cfg.TmdbApiKey) { _tfTmdbKey = cfg.TmdbApiKey; sLog('TF_KEY_CUSTOM', _tfTmdbKey.substring(0, 8) + '...'); }
else { sLog('TF_KEY_DEFAULT'); }
} catch(e) {}
}
cb();
};
xhr2.onerror = function() { cb(); };
xhr2.send();
} catch(e) { cb(); }
};
xhr.onerror = function() { cb(); };
xhr.send();
}
function _tfCheck() {
if (_tfTimer) clearTimeout(_tfTimer);
_tfTimer = setTimeout(_tfDoCheck, 2000);
}
function _tfDoCheck() {
var hash = window.location.hash || window.location.href;
if (hash.indexOf('details') === -1 && hash.indexOf('item') === -1) return;
var existing = document.querySelector('.btnPlayTrailer, [data-action="playtrailer"]');
if (existing) { sLog('TF_HAS_TRAILER'); return; }
var match = hash.match(/[?&]id=([^&]+)/);
if (!match) return;
var itemId = match[1];
var api = window.ApiClient;
if (!api) { sLog('TF_NO_API'); return; }
// Fetch TMDB key first (cached after first call), then proceed
_tfFetchTmdbKey(api, function() {
sLog('TF_CHECK', { itemId: itemId });
var xhr = new XMLHttpRequest();
var url = api.serverAddress() + '/Users/' + api.getCurrentUserId() + '/Items/' + itemId + '?Fields=ProviderIds,RemoteTrailers';
xhr.open('GET', url, true);
xhr.setRequestHeader('Authorization', 'MediaBrowser Token="' + api.accessToken() + '"');
xhr.onload = function() {
if (xhr.status !== 200) return;
try {
var item = JSON.parse(xhr.responseText);
if (item.Type !== 'Movie' && item.Type !== 'Series') return;
if (item.RemoteTrailers && item.RemoteTrailers.length > 0) { sLog('TF_HAS_REMOTE'); return; }
if (item.LocalTrailerCount && item.LocalTrailerCount > 0) { sLog('TF_HAS_LOCAL'); return; }
var tmdbId = (item.ProviderIds && item.ProviderIds.Tmdb) || '';
var title = item.Name || '';
var year = item.ProductionYear || '';
var lang = (document.documentElement.lang || navigator.language || 'en').split('-')[0];
if (!tmdbId && !title) return;
sLog('TF_SEARCH', { tmdbId: tmdbId, title: title, lang: lang });
var svcUrl = SERVICE_BASE + '/trailer?tmdbId=' + encodeURIComponent(tmdbId) + '&title=' + encodeURIComponent(title) + '&year=' + encodeURIComponent(year) + '&lang=' + encodeURIComponent(lang);
if (_tfTmdbKey) svcUrl += '&tmdbKey=' + encodeURIComponent(_tfTmdbKey);
var xhr2 = new XMLHttpRequest();
xhr2.open('GET', svcUrl, true);
xhr2.onload = function() {
if (xhr2.status !== 200) return;
try {
var r = JSON.parse(xhr2.responseText);
if (r.videoKey) {
sLog('TF_FOUND', { key: r.videoKey, source: r.source });
_tfInjectBtn(r.videoKey);
} else { sLog('TF_NOT_FOUND'); }
} catch(e) { sLog('TF_ERR', e.message); }
};
xhr2.send();
} catch(e) { sLog('TF_ITEM_ERR', e.message); }
};
xhr.send();
});
}
function _tfInjectBtn(videoKey) {
var container = document.querySelector('.mainDetailButtons, .detailButtons');
if (!container || document.querySelector('.btnTrailerInjected')) return;
var btn = document.createElement('button');
btn.setAttribute('is', 'emby-button');
btn.setAttribute('type', 'button');
btn.className = 'button-flat btnPlayTrailer btnTrailerInjected detailButton emby-button';
var icon = document.createElement('span');
icon.className = 'material-icons detailButton-icon';
icon.textContent = 'theaters';
var wrap = document.createElement('span');
wrap.className = 'detailButton-content';
var txt = document.createElement('span');
txt.className = 'button-text';
txt.textContent = 'Trailer';
wrap.appendChild(txt);
btn.appendChild(icon);
btn.appendChild(wrap);
btn.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
sLog('TF_PLAY', { key: videoKey });
_tfPlayOverlay(videoKey);
});
var first = container.querySelector('button, .detailButton');
if (first && first.nextSibling) container.insertBefore(btn, first.nextSibling);
else container.appendChild(btn);
sLog('TF_BTN_INJECTED');
}
function _tfPlayOverlay(videoKey) {
var overlay = document.createElement('div');
overlay.id = 'trailerOverlay';
overlay.style.cssText = 'position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:2147483647;background:#000;';
var iframe = document.createElement('iframe');
iframe.style.cssText = 'width:100%;height:100%;border:0;';
iframe.setAttribute('allow', 'autoplay; encrypted-media; fullscreen');
iframe.src = SERVICE_BASE + '/player.html?videoId=' + encodeURIComponent(videoKey);
overlay.appendChild(iframe);
document.body.appendChild(overlay);
var msgH = function(ev) {
if (!ev.data || !ev.data.__ytbridge) return;
if (ev.data.type === 'state' && ev.data.data === 0) _tfClose();
};
window.addEventListener('message', msgH);
var keyH = function(ev) {
if (ev.keyCode === 10009 || ev.keyCode === 27 || ev.keyCode === 8) {
ev.preventDefault(); ev.stopPropagation(); _tfClose();
}
};
document.addEventListener('keydown', keyH, true);
function _tfClose() {
window.removeEventListener('message', msgH);
document.removeEventListener('keydown', keyH, true);
try { if (iframe.contentWindow) iframe.contentWindow.postMessage({ __ytbridge_cmd: true, cmd: 'stop' }, '*'); } catch(e) {}
if (overlay.parentNode) overlay.parentNode.removeChild(overlay);
sLog('TF_CLOSED');
}
}
document.addEventListener('viewshow', _tfCheck);
sLog('INIT_COMPLETE');
})();
""".Replace("http://localhost:8123", $"http://localhost:{servicePort}");
foreach (var file in candidates)
{
var content = await File.ReadAllTextAsync(file);
if (content.Contains("__YT_FIX_V17__")) continue;
await File.WriteAllTextAsync(file, injected + "\n" + content, utf8NoBom);
}
}
// 2. CREATE THE NODE.JS SERVICE (with trailer fallback endpoint)
public async Task CreateYouTubeResolverAsync(PackageWorkspace ws)
{
int servicePort = await ResolveServicePortAsync(ws);
var utf8NoBom = new UTF8Encoding(false);
string serviceDir = Path.Combine(ws.Root, "service");
string serviceJsPath = Path.Combine(serviceDir, "service.js");
if (!Directory.Exists(serviceDir)) Directory.CreateDirectory(serviceDir);
string serviceJsContent = """
var http = require('http');
var https = require('https');
var urlMod = require('url');
var PORT = 8123;
var LISTEN_HOST = '0.0.0.0';
var LOGS = [];
var TMDB_KEY = '4219e299c89411838049ab0dab19ebd5'; // fallback key from Jellyfin TmdbUtils.cs, used when runtime key extraction fails
function log(msg, data) {
var line = new Date().toISOString() + ' ' + msg + ' ' + (data ? JSON.stringify(data) : '');
LOGS.push(line);
if (LOGS.length > 2000) LOGS.shift();
console.log(line);
}
function write(res, code, contentType, body, additionalHeaders) {
var headers = {
'Content-Type': contentType,
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Allow-Private-Network': 'true',
'Cache-Control': 'no-store'
};
if (additionalHeaders) Object.assign(headers, additionalHeaders);
res.writeHead(code, headers);
res.end(body);
}
// ========================================================================
// TRAILER FALLBACK HELPERS - TMDB + DuckDuckGo Lite
// ========================================================================
function httpsGet(reqUrl, cb) {
var done = false;
function finish(err, status, body) { if (done) return; done = true; cb(err, status, body); }
var parsed = urlMod.parse(reqUrl);
var opts = {
hostname: parsed.hostname,
path: parsed.path,
port: 443,
method: 'GET',
rejectUnauthorized: false,
headers: { 'User-Agent': 'JellyfinSamsungTV/1.0' }
};
var req = https.request(opts, function(resp) {
var body = '';
resp.on('data', function(c) { body += c; });
resp.on('end', function() { finish(null, resp.statusCode, body); });
});
req.on('error', function(e) { finish(e); });
req.setTimeout(8000, function() { req.abort(); });
req.end();
}
function fetchTmdbTrailers(tmdbId, lang, apiKey, cb) {
if (!tmdbId) return cb(null, { langKey: null, enKey: null });
var key = apiKey || TMDB_KEY;
var u = 'https://api.themoviedb.org/3/movie/' + tmdbId +
'?api_key=' + key +
'&language=' + lang +
'&append_to_response=videos' +
'&include_video_language=' + lang + ',en,null';
log('TMDB_FETCH ' + u);
httpsGet(u, function(err, status, body) {
if (err || status !== 200) { log('TMDB_ERR ' + (err ? err.message : status)); return cb(null, { langKey: null, enKey: null }); }
try {
var data = JSON.parse(body);
var vids = (data.videos && data.videos.results) || [];
var trailers = [];
for (var i = 0; i < vids.length; i++) {
if (vids[i].site === 'YouTube' && (vids[i].type === 'Trailer' || vids[i].type === 'Teaser')) trailers.push(vids[i]);
}
var langPick = null, enPick = null;
for (var j = 0; j < trailers.length; j++) {
if (!langPick && trailers[j].iso_639_1 === lang) langPick = trailers[j];
if (!enPick && trailers[j].iso_639_1 === 'en') enPick = trailers[j];
}
log('TMDB_RESULT langKey=' + (langPick ? langPick.key : 'null') + ' enKey=' + (enPick ? enPick.key : 'null'));
cb(null, {
langKey: langPick ? langPick.key : null,
enKey: enPick ? enPick.key : null
});
} catch(e) { log('TMDB_PARSE_ERR ' + e.message); cb(null, { langKey: null, enKey: null }); }
});
}
function searchDdg(title, year, lang, cb) {
var langMap = __LANG_MAP__;
var langName = langMap[lang] || '';
var langKeywords = langName.toLowerCase().split(' ');
var q = title + (year ? ' ' + year : '') + ' Trailer ' + langName + ' site:youtube.com';
var encoded = encodeURIComponent(q).replace(/%20/g, '+');
var u = 'https://lite.duckduckgo.com/lite/?q=' + encoded;
log('DDG_FETCH q=' + q);
httpsGet(u, function(err, status, body) {
if (err || status !== 200) { log('DDG_ERR ' + (err ? err.message : status)); return cb(null, { langKey: null, fallbackKey: null }); }
var decoded = body.replace(/%2F/gi, '/').replace(/%3F/gi, '?').replace(/%3D/gi, '=').replace(/%26/gi, '&');
// Extract results with titles: find <a> tags followed by youtube URLs
var linkRe = /<a[^>]+href="([^"]*youtube\.com\/watch\?v=([a-zA-Z0-9_\-]{11})[^"]*)"[^>]*>([^<]*)<\/a>/gi;
var m, seen = {}, results = [];
while ((m = linkRe.exec(decoded)) !== null) {
if (!seen[m[2]]) {
seen[m[2]] = true;
results.push({ key: m[2], title: m[3] });
}
}
// Also catch URL-encoded links
var encRe = /href="[^"]*youtube\.com%2Fwatch%3Fv%3D([a-zA-Z0-9_\-]{11})[^"]*"[^>]*>([^<]*)<\/a>/gi;
while ((m = encRe.exec(body)) !== null) {
if (!seen[m[1]]) {
seen[m[1]] = true;
results.push({ key: m[1], title: m[2] });
}
}
if (!results.length) { log('DDG_NO_RESULTS'); return cb(null, { langKey: null, fallbackKey: null }); }
// Split: language-matched vs non-matched results
var langMatch = null;
var fallback = null;
for (var i = 0; i < results.length; i++) {
var t = results[i].title.toLowerCase();
var isLangMatch = false;
for (var j = 0; j < langKeywords.length; j++) {
if (langKeywords[j] && t.indexOf(langKeywords[j]) !== -1) { isLangMatch = true; break; }
}
if (isLangMatch && !langMatch) langMatch = results[i];
if (!isLangMatch && !fallback) fallback = results[i];
if (langMatch && fallback) break;
}
log('DDG_FOUND langKey=' + (langMatch ? langMatch.key : 'null') + ' fallbackKey=' + (fallback ? fallback.key : 'null') + ' total=' + results.length);
cb(null, { langKey: langMatch ? langMatch.key : null, fallbackKey: fallback ? fallback.key : null });
});
}
// ========================================================================
var PLAYER_HTML = `
<!doctype html>
<html>
<head>
<style>html,body{margin:0;padding:0;background:#000;width:100%;height:100%;overflow:hidden;}</style>
</head>
<body>
<div id="player" style="width:100%;height:100%;"></div>
<script>
var VID = new URLSearchParams(window.location.search).get('videoId');
function post(type, data, t, d, s) {
window.parent.postMessage({ __ytbridge: true, type: type, data: data, t: t||0, d: d||0, s: s||-1 }, '*');
}
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
document.head.appendChild(tag);
var player;
var autoplayAttempted = false;
window.onYouTubeIframeAPIReady = function() {
player = new YT.Player('player', {
height: '100%', width: '100%', videoId: VID,
playerVars: {
'autoplay': 1,
'controls': 0,
'enablejsapi': 1,
'origin': 'http://localhost:8123',
'playsinline': 1,
'mute': 0
},
events: {
'onReady': function(ev) {
post('ready');
// Ensure autoplay starts
if (!autoplayAttempted) {
autoplayAttempted = true;
setTimeout(function() {
if (player && player.playVideo) {
player.playVideo();
}
}, 100);
}
setInterval(function(){
if(player && player.getCurrentTime)
post('time', null, player.getCurrentTime()*1000, player.getDuration()*1000, player.getPlayerState());
}, 500);
},
'onStateChange': function(ev) { post('state', ev.data); },
'onError': function(ev) { post('error', ev.data); }
}
});
};
window.addEventListener('message', function(ev) {
if (!ev.data || !ev.data.__ytbridge_cmd || !player) return;
var m = ev.data;
if (m.cmd === 'play') player.playVideo();
else if (m.cmd === 'pause') player.pauseVideo();
else if (m.cmd === 'stop') player.stopVideo();
else if (m.cmd === 'seek') player.seekTo(m.val / 1000, true);
else if (m.cmd === 'volume') player.setVolume(m.val);
else if (m.cmd === 'mute') {
if (m.val) player.mute();
else player.unMute();
}
});
</script>
</body>
</html>
`;
function handler(req, res) {
var u = urlMod.parse(req.url, true);
if (req.method === 'OPTIONS') return write(res, 204, 'text/plain', '');
if (u.pathname === '/log') {
var body = '';
req.on('data', function(c) { body += c; });
req.on('end', function() {
try {
var j = JSON.parse(body);
log(j.args ? j.args.join(' ') : 'LOG');
} catch(e){}
write(res, 200, 'application/json', '{}');
});
return;
}
if (u.pathname === '/debug/logs') return write(res, 200, 'application/json', JSON.stringify({logs: LOGS}));
if (u.pathname === '/player.html') {
return write(res, 200, 'text/html', PLAYER_HTML, { 'Referrer-Policy': 'no-referrer-when-downgrade' });
}
// ====================================================================
// TRAILER FALLBACK ENDPOINT
// 1 TMDB call fetches both lang + en, then: lang(TMDB) -> lang(DDG) -> en(CACHED) -> en(CACHED)
// ====================================================================
if (u.pathname === '/trailer') {
var tId = u.query.tmdbId || '';
var tTitle = u.query.title || '';
var tYear = u.query.year || '';
var tLang = u.query.lang || 'en';
var tKey = u.query.tmdbKey || '';
log('TRAILER_REQ tmdbId=' + tId + ' title=' + tTitle + ' lang=' + tLang + ' customKey=' + (tKey ? 'yes' : 'no'));
// Single TMDB call: fetches both user-language and English trailers
fetchTmdbTrailers(tId, tLang, tKey, function(e1, tmdb) {
// Step 1: TMDB user-language trailer (cached)
if (tmdb.langKey) return write(res, 200, 'application/json', JSON.stringify({videoKey:tmdb.langKey, source:'tmdb_'+tLang}));
// Single DDG call: returns both language-matched and fallback keys
searchDdg(tTitle, tYear, tLang, function(e2, ddg) {
// Step 2: DDG language-matched trailer (cached)
if (ddg.langKey) return write(res, 200, 'application/json', JSON.stringify({videoKey:ddg.langKey, source:'ddg_'+tLang}));
if (tLang !== 'en') {
// Step 3: TMDB English fallback (cached from step 1, no extra call)
if (tmdb.enKey) return write(res, 200, 'application/json', JSON.stringify({videoKey:tmdb.enKey, source:'tmdb_en_fallback'}));
// Step 4: DDG non-language result as English fallback (cached from step 2, no extra call)
if (ddg.fallbackKey) return write(res, 200, 'application/json', JSON.stringify({videoKey:ddg.fallbackKey, source:'ddg_en_fallback'}));
}
write(res, 200, 'application/json', JSON.stringify({videoKey:null, source:null}));
});
});
return;
}
return write(res, 404, 'text/plain', 'Not Found');
}
var server = http.createServer(handler);
server.listen(PORT, LISTEN_HOST, function() { log('SERVER LISTENING ' + LISTEN_HOST + ':' + PORT); });
""".Replace("var PORT = 8123", $"var PORT = {servicePort}")
.Replace("'origin': 'http://localhost:8123'", $"'origin': 'http://localhost:{servicePort}'")
.Replace("__LANG_MAP__", TrailerLanguageMap.JsObject);
await File.WriteAllTextAsync(serviceJsPath, serviceJsContent, utf8NoBom);
}
// 3. UPDATE CONFIG.XML
public async Task UpdateCorsAsync(PackageWorkspace ws)
{
int servicePort = await ResolveServicePortAsync(ws);
var path = Path.Combine(ws.Root, "config.xml");
if (!File.Exists(path)) return;
var doc = XDocument.Load(path);
XNamespace ns = "http://www.w3.org/ns/widgets";
XNamespace tizen = "http://tizen.org/ns/widgets";
doc.Root.Elements(ns + "access").Remove();
doc.Root.Elements(ns + "allow-navigation").Remove();
doc.Root.Elements(tizen + "allow-navigation").Remove();
doc.Root.Elements(tizen + "content-security-policy").Remove();
doc.Root.Add(new XElement(ns + "access", new XAttribute("origin", "*"), new XAttribute("subdomains", "true")));
doc.Root.Add(new XElement(ns + "allow-navigation", new XAttribute("href", "*")));
doc.Root.Add(new XElement(tizen + "allow-navigation", "*"));
var serviceId = "ytresolver";
if (!doc.Descendants(tizen + "service").Any(x => x.Attribute("name")?.Value == serviceId))
{
var pkgId = doc.Root.Element(tizen + "application")?.Attribute("package")?.Value ?? "AprZAARz4r";
doc.Root.Add(new XElement(tizen + "service",
new XAttribute("id", pkgId + "." + serviceId),
new XAttribute("type", "service"),
new XElement(tizen + "content", new XAttribute("src", "service/service.js")),
new XElement(tizen + "name", serviceId)
));
}
// UpdateCorsAsync - the CSP string is short so interpolation is fine there since there's no JS braces involved:
string csp = $"default-src * 'unsafe-inline' 'unsafe-eval' data: blob:; " +
$"script-src * 'unsafe-inline' 'unsafe-eval' http://localhost:{servicePort} https://www.youtube.com; " +
$"frame-src * http://localhost:{servicePort} https://www.youtube.com; " +
$"connect-src * http://localhost:{servicePort};";
doc.Root.Add(new XElement(tizen + "content-security-policy", csp));
doc.Root.Add(new XElement(tizen + "allow-mixed-content", "true"));
var privs = new[] {
"http://tizen.org/privilege/internet",
"http://tizen.org/privilege/network.public",
"http://tizen.org/privilege/content.read"
};
foreach (var p in privs)
{
if (!doc.Descendants(tizen + "privilege").Any(x => x.Attribute("name")?.Value == p))
doc.Root.Add(new XElement(tizen + "privilege", new XAttribute("name", p)));
}
doc.Save(path);
}
}
}

View File

@@ -0,0 +1,191 @@
using Jellyfin2Samsung.Helpers.API;
using Jellyfin2Samsung.Helpers.Core;
using Jellyfin2Samsung.Helpers.Jellyfin.Plugins;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
namespace Jellyfin2Samsung.Helpers.Jellyfin.Patches
{
public class JellyfinIndex(
HttpClient http,
JellyfinApiClient api,
PluginManager plugins)
{
private readonly JellyfinApiClient _apiClient = api;
private readonly JellyfinPluginPatcher _plugins = new(http, api, plugins);
public async Task PatchIndexAsync(PackageWorkspace ws, string serverUrl)
{
string index = Path.Combine(ws.Root, "www", "index.html");
if (!File.Exists(index)) return;
var html = await File.ReadAllTextAsync(index);
html = HtmlUtils.EnsureBaseHref(html);
html = HtmlUtils.RewriteLocalPaths(html);
var css = new StringBuilder();
var headJs = new StringBuilder();
var bodyJs = new StringBuilder();
await _plugins.PatchPluginsAsync(ws, serverUrl, css, headJs, bodyJs);
html = html.Replace("</head>", css + "\n" + headJs + "\n</head>");
html = html.Replace("</body>", bodyJs + "\n</body>");
html = HtmlUtils.CleanAndApplyCsp(html);
html = HtmlUtils.EnsurePublicJsIsLast(html);
await File.WriteAllTextAsync(index, html);
}
public async Task UpdateServerAddressAsync(PackageWorkspace ws)
{
string path = Path.Combine(ws.Root, "www", "config.json");
JsonObject config;
if (File.Exists(path))
{
var json = await File.ReadAllTextAsync(path);
config = JsonNode.Parse(json)?.AsObject()
?? new JsonObject();
}
else
{
config = new JsonObject();
}
// Ensure multiserver is set
config["multiserver"] = false;
// Ensure servers array exists
if (config["servers"] is not JsonArray servers)
{
servers = new JsonArray();
config["servers"] = servers;
}
var serverUrl = UrlHelper.NormalizeServerUrl(AppSettings.Default.JellyfinFullUrl);
// Avoid duplicates
if (!servers.Any(s => s?.GetValue<string>() == serverUrl))
servers.Add(serverUrl);
// Add LocalAddress (IP-based) as fallback when the primary URL uses mDNS (.local)
// Samsung TVs (Tizen) cannot reliably resolve mDNS hostnames, especially after network disruptions
var localAddress = UrlHelper.NormalizeServerUrl(AppSettings.Default.JellyfinServerLocalAddress);
if (!string.IsNullOrEmpty(localAddress) &&
localAddress != serverUrl &&
UrlHelper.IsValidHttpUrl(localAddress) &&
!servers.Any(s => s?.GetValue<string>() == localAddress))
{
servers.Add(localAddress);
Trace.WriteLine($"[UpdateServerAddress] Added LocalAddress fallback: {localAddress}");
}
await File.WriteAllTextAsync(path, config.ToJsonString());
}
/// <summary>
/// Injects auto-login credentials into the Jellyfin web app.
/// This stores the access token and server info in localStorage format.
/// Uses the real server ID from /System/Info/Public to prevent ServerMismatch errors.
/// </summary>
public async Task InjectAutoLoginAsync(PackageWorkspace ws)
{
var accessToken = AppSettings.Default.JellyfinAccessToken;
var userId = AppSettings.Default.JellyfinUserId;
var serverUrl = UrlHelper.NormalizeServerUrl(AppSettings.Default.JellyfinFullUrl);
var serverId = AppSettings.Default.JellyfinServerId;
var localAddress = AppSettings.Default.JellyfinServerLocalAddress;
var serverName = AppSettings.Default.JellyfinServerName;
if (string.IsNullOrEmpty(accessToken) || string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(serverUrl))
{
Trace.WriteLine("[InjectAutoLogin] Missing credentials, skipping auto-login injection");
return;
}
// If server ID or server name is not stored, try to fetch it now
if (string.IsNullOrEmpty(serverId) || string.IsNullOrEmpty(serverName))
{
Trace.WriteLine("[InjectAutoLogin] Server ID/Name not cached, fetching from server...");
var serverInfo = await _apiClient.GetPublicSystemInfoAsync(serverUrl);
if (serverInfo != null && !string.IsNullOrEmpty(serverInfo.Id))
{
serverId = serverInfo.Id;
localAddress = serverInfo.LocalAddress ?? "";
serverName = serverInfo.ServerName ?? "";
AppSettings.Default.JellyfinServerId = serverId;
AppSettings.Default.JellyfinServerLocalAddress = localAddress;
AppSettings.Default.JellyfinServerName = serverName;
AppSettings.Default.Save();
Trace.WriteLine($"[InjectAutoLogin] Fetched and stored server ID: {serverId}, Name: {serverName}");
}
else
{
Trace.WriteLine("[InjectAutoLogin] WARNING: Could not fetch server ID, auto-login may fail with ServerMismatch");
return;
}
}
string indexPath = Path.Combine(ws.Root, "www", "index.html");
if (!File.Exists(indexPath))
{
Trace.WriteLine("[InjectAutoLogin] index.html not found");
return;
}
var html = await File.ReadAllTextAsync(indexPath);
// Create the credentials object that Jellyfin web expects
// Using the REAL server ID (GUID) from /System/Info/Public to prevent ServerMismatch
var credentialsScript = new StringBuilder();
credentialsScript.AppendLine("<script>");
credentialsScript.AppendLine("(function() {");
credentialsScript.AppendLine(" try {");
credentialsScript.AppendLine($" var serverUrl = '{HtmlUtils.EscapeJsString(serverUrl)}';");
credentialsScript.AppendLine($" var serverId = '{HtmlUtils.EscapeJsString(serverId)}';");
credentialsScript.AppendLine($" var localAddress = '{HtmlUtils.EscapeJsString(localAddress)}';");
credentialsScript.AppendLine($" var serverName = '{HtmlUtils.EscapeJsString(serverName)}';");
credentialsScript.AppendLine($" var userId = '{HtmlUtils.EscapeJsString(userId)}';");
credentialsScript.AppendLine($" var accessToken = '{HtmlUtils.EscapeJsString(accessToken)}';");
credentialsScript.AppendLine();
credentialsScript.AppendLine(" // Create credentials object matching Jellyfin's expected format");
credentialsScript.AppendLine(" // Using real server ID (GUID) from /System/Info/Public");
credentialsScript.AppendLine(" var credentials = {");
credentialsScript.AppendLine(" Servers: [{");
credentialsScript.AppendLine(" Name: serverName || serverUrl,");
credentialsScript.AppendLine(" ManualAddress: serverUrl,");
credentialsScript.AppendLine(" LocalAddress: localAddress || serverUrl,");
credentialsScript.AppendLine(" Id: serverId,");
credentialsScript.AppendLine(" UserId: userId,");
credentialsScript.AppendLine(" AccessToken: accessToken,");
credentialsScript.AppendLine(" DateLastAccessed: new Date().getTime()");
credentialsScript.AppendLine(" }]");
credentialsScript.AppendLine(" };");
credentialsScript.AppendLine();
credentialsScript.AppendLine(" // Store in localStorage");
credentialsScript.AppendLine(" localStorage.setItem('jellyfin_credentials', JSON.stringify(credentials));");
credentialsScript.AppendLine();
credentialsScript.AppendLine(" console.log('[Auto-Login] Credentials injected for server: ' + serverName + ' (' + serverUrl + ') with ID: ' + serverId);");
credentialsScript.AppendLine(" } catch(e) {");
credentialsScript.AppendLine(" console.error('[Auto-Login] Failed to inject credentials:', e);");
credentialsScript.AppendLine(" }");
credentialsScript.AppendLine("})();");
credentialsScript.AppendLine("</script>");
// Inject before </head> to ensure it runs before Jellyfin's scripts
html = html.Replace("</head>", credentialsScript + "\n</head>");
await File.WriteAllTextAsync(indexPath, html);
Trace.WriteLine($"[InjectAutoLogin] Auto-login credentials injected successfully with server ID: {serverId}");
}
}
}

View File

@@ -0,0 +1,103 @@
namespace Jellyfin2Samsung.Helpers.Jellyfin.Fixes
{
/// <summary>
/// Maps ISO 639 language codes to search keywords for DuckDuckGo Lite trailer search.
/// Covers all languages supported by Jellyfin's localization system.
/// Each entry contains the English name + native name for maximum DDG search match rate.
/// </summary>
public static class TrailerLanguageMap
{
public const string JsObject = @"{
ab:'Abkhaz Аԥсуа',
af:'Afrikaans',
ar:'Arabic العربية',
as:'Assamese অসমীয়া',
be:'Belarusian Беларуская',
bg:'Bulgarian Български',
bn:'Bengali বাংলা',
ca:'Catalan Català',
chr:'Cherokee ᏣᎳᎩ',
cs:'Czech Český',
cy:'Welsh Cymraeg',
da:'Danish Dansk',
de:'Deutsch German',
el:'Greek Ελληνικά',
en:'English',
enm:'English',
eo:'Esperanto',
es:'Spanish Español',
et:'Estonian Eesti',
eu:'Basque Euskara',
fa:'Persian Farsi فارسی',
fi:'Finnish Suomi',
fil:'Filipino Tagalog',
fo:'Faroese Føroyskt',
fr:'French Français',
ga:'Irish Gaeilge',
gl:'Galician Galego',
gsw:'Swiss German Schweizerdeutsch',
he:'Hebrew עברית',
hi:'Hindi हिन्दी',
hr:'Croatian Hrvatski',
ht:'Haitian Creole Kreyòl',
hu:'Hungarian Magyar',
hy:'Armenian Հայերեն',
id:'Indonesian Bahasa',
is:'Icelandic Íslenska',
it:'Italian Italiano',
ja:'Japanese 日本語',
jbo:'Lojban',
ka:'Georgian ქართული',
kab:'Kabyle Taqbaylit',
kk:'Kazakh Қазақша',
km:'Khmer ភាសាខ្មែរ',
kn:'Kannada ಕನ್ನಡ',
ko:'Korean 한국어',
kw:'Cornish Kernewek',
ky:'Kyrgyz Кыргызча',
lb:'Luxembourgish Lëtzebuergesch',
lt:'Lithuanian Lietuvių',
lv:'Latvian Latviešu',
lzh:'Chinese 中文',
mi:'Maori Māori',
mk:'Macedonian Македонски',
ml:'Malayalam മലയാളം',
mn:'Mongolian Монгол',
mr:'Marathi मराठी',
ms:'Malay Melayu',
mt:'Maltese Malti',
my:'Burmese Myanmar မြန်မာ',
nb:'Norwegian Norsk',
ne:'Nepali नेपाली',
nl:'Dutch Nederlands',
nn:'Norwegian Norsk',
oc:'Occitan',
or:'Odia ଓଡ଼ିଆ',
pa:'Punjabi ਪੰਜਾਬੀ',
pl:'Polish Polski',
pr:'English',
pt:'Portuguese Português',
ro:'Romanian Română',
ru:'Russian Русский',
si:'Sinhala සිංහල',
sk:'Slovak Slovenský',
sl:'Slovenian Slovenščina',
sn:'Shona',
sq:'Albanian Shqip',
sr:'Serbian Српски',
sv:'Swedish Svenska',
sw:'Swahili Kiswahili',
ta:'Tamil தமிழ்',
te:'Telugu తెలుగు',
th:'Thai ไทย',
tr:'Turkish Türkçe',
ug:'Uyghur ئۇيغۇرچە',
uk:'Ukrainian Українська',
ur:'Urdu اردو',
uz:'Uzbek Oʻzbekcha',
vi:'Vietnamese Tiếng Việt',
zh:'Chinese 中文',
zu:'Zulu isiZulu'
}";
}
}

View File

@@ -0,0 +1,178 @@
namespace Jellyfin2Samsung.Helpers.Jellyfin.Plugins.EditorsChoice
{
public class PatchEditorsChoice
{
public string ApplyAsync(string js)
{
string css = @"
.hss-hero {
position: relative;
width: 94%;
height: 500px !important;
margin: 30px auto 30px auto !important;
overflow: hidden;
border-radius: 15px;
background: #000;
border: 0.06em solid var(--borderColor) !important;
}
.hss-bg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
opacity: 0;
transition: opacity 1s;
z-index: 1;
}
.hss-overlay {
position: absolute;
inset: 0;
background: linear-gradient(
90deg,
rgba(0, 0, 0, 1) 0%,
rgba(0, 0, 0, 0) 60%,
rgba(0, 0, 0, 0) 100%
) !important;
z-index: 2;
}
.hss-content {
position: absolute;
inset: 0;
padding: 30px !important;
display: flex;
flex-direction: column;
justify-content: center;
z-index: 3;
pointer-events: none;
}
.hss-logo {
max-width: 400px;
max-height: 120px;
object-fit: contain;
margin-bottom: 15px;
pointer-events: auto;
}
.hss-logo + div,
.hss-rate {
color: #fff !important;
font-size: 1em !important;
font-weight: 400 !important;
height: 10px !important;
}
.hss-overview {
color: #eee !important;
width: 45%;
font-size: 0.9em !important;
line-height: 1.5;
margin-bottom: 25px !important;
display: -webkit-box;
-webkit-line-clamp: 4;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis !important;
pointer-events: auto;
}
.hss-btn {
background: #fff !important;
color: #000 !important;
border: none;
padding: 0.9em 1em !important;
border-radius: 0.5em !important;
font-weight: 500 !important;
font-size: 1em !important;
text-transform: none !important;
cursor: pointer;
pointer-events: auto;
width: fit-content;
box-shadow: none !important;
}
.hss-btn:focus {
background: var(--highlightOutlineColor) !important;
color: #fff !important;
transform: none !important;
outline: none;
}
.editorsChoiceItemBanner,
.editorsChoiceItemsContainer {
display: none !important;
}
";
return @"
(function () {
var style = document.createElement('style');
style.innerText = `" + css.Replace("\r\n", " ").Replace("\"", "\\\"") + @"`;
document.head.appendChild(style);
function init() {
var api = window.ApiClient || (window.ConnectionManager && window.ConnectionManager.getcurrentitem().apiClient);
var container = document.querySelector('.sections') || document.querySelector('.homeSectionsContainer');
if (!api || !container || document.getElementById('hss-hero')) return;
api.getItems(api.getCurrentUserId(), { IncludeItemTypes: 'Movie', SortBy: 'Random', SortOrder: 'Descending', Limit: 8, Recursive: true, Fields: 'Overview,ImageTags,CommunityRating' }).then(function(res) {
var items = res.Items || [];
if (!items.length) return;
var html = `
<div id=""hss-hero"" class=""hss-hero"">
<img class=""hss-bg"" crossorigin=""anonymous"">
<div class=""hss-overlay""></div>
<div class=""hss-content"">
<img class=""hss-logo"" crossorigin=""anonymous"">
<div style=""color:#f5c518; font-size:1.6em; font-weight:bold; margin-bottom:10px;"">⭐ <span class=""hss-rate""></span></div>
<p class=""hss-overview""></p>
<button class=""hss-btn"">Watch Now</button>
</div>
</div>`;
container.insertAdjacentHTML('afterbegin', html);
var idx = 0;
function slide() {
var item = items[idx];
var el = document.getElementById('hss-hero');
var host = api.serverAddress();
var token = api.accessToken();
var bg = el.querySelector('.hss-bg');
bg.style.opacity = '0';
setTimeout(function() {
bg.src = host + '/Items/' + item.Id + '/Images/Backdrop/0?api_key=' + token;
bg.onload = function() { bg.style.opacity = '0.8'; };
var logo = el.querySelector('.hss-logo');
if (item.ImageTags.Logo) {
logo.src = host + '/Items/' + item.Id + '/Images/Logo/0?api_key=' + token;
logo.style.display = 'block';
} else { logo.style.display = 'none'; }
el.querySelector('.hss-rate').innerText = (item.CommunityRating || '7.5');
el.querySelector('.hss-overview').innerText = item.Overview || '';
el.querySelector('.hss-btn').onclick = function() {
if(window.Emby && window.Emby.Page) window.Emby.Page.showItem(item.Id);
else window.location.hash = '#!/item?id=' + item.Id;
};
idx = (idx + 1) % items.length;
}, 400);
}
slide();
setInterval(slide, 10000);
});
}
setInterval(init, 2000);
})();";
}
}
}

View File

@@ -0,0 +1,233 @@
using Jellyfin2Samsung.Helpers.API;
using Jellyfin2Samsung.Helpers.Core;
using Jellyfin2Samsung.Models;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace Jellyfin2Samsung.Helpers.Jellyfin.Plugins
{
/// <summary>
/// Orchestrates plugin-related patching:
/// - Fetch server index
/// - Cache plugin assets referenced by index.html
/// - Apply API-installed plugins using PluginManager + plugin patch classes
/// </summary>
public class JellyfinPluginPatcher
{
private readonly HttpClient _httpClient;
private readonly JellyfinApiClient _apiClient;
private readonly PluginManager _pluginManager;
private readonly HashSet<string> _injectedScripts = new(StringComparer.OrdinalIgnoreCase);
private readonly HashSet<string> _injectedStyles = new(StringComparer.OrdinalIgnoreCase);
public JellyfinPluginPatcher(
HttpClient httpClient,
JellyfinApiClient apiClient,
PluginManager pluginManager)
{
_httpClient = httpClient;
_apiClient = apiClient;
_pluginManager = pluginManager;
}
public async Task PatchPluginsAsync(
PackageWorkspace workspace,
string serverUrl,
StringBuilder cssBuilder,
StringBuilder headJsBuilder,
StringBuilder bodyJsBuilder)
{
string pluginCacheDir = Path.Combine(workspace.Root, "www", "plugin_cache");
Directory.CreateDirectory(pluginCacheDir);
string serverHtml = await FetchServerIndexAsync(serverUrl);
if (!string.IsNullOrWhiteSpace(serverHtml))
{
await CacheServerAssetsAsync(
serverHtml,
serverUrl,
pluginCacheDir,
cssBuilder,
bodyJsBuilder);
}
await ApplyApiInstalledPluginsAsync(
workspace,
serverUrl,
pluginCacheDir,
cssBuilder,
headJsBuilder,
bodyJsBuilder);
}
private async Task<string> FetchServerIndexAsync(string serverUrl)
{
try
{
var url = UrlHelper.CombineUrl(serverUrl, "/web/index.html");
Trace.WriteLine($"▶ Fetching server index.html: {url}");
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(8));
return await _httpClient.GetStringAsync(url, cts.Token);
}
catch (Exception ex)
{
Trace.WriteLine($"⚠ Failed to fetch server index.html: {ex}");
return string.Empty;
}
}
private async Task CacheServerAssetsAsync(
string serverHtml,
string serverUrl,
string pluginCacheDir,
StringBuilder cssBuilder,
StringBuilder jsBuilder)
{
Trace.WriteLine("▶ Extracting plugin assets from server index…");
// --- CSS ---
var cssMatches = RegexPatterns.Html.LinkHref.Matches(serverHtml);
foreach (Match m in cssMatches)
{
string href = m.Groups[1].Value;
if (string.IsNullOrWhiteSpace(href)) continue;
if (!_pluginManager.TryClassifyServerAsset(href, out var kind) || kind != ServerAssetKind.PluginAsset)
continue;
await CacheCssAsync(serverUrl, href, pluginCacheDir, cssBuilder);
}
// --- JS ---
var jsMatches = RegexPatterns.Html.ScriptSrc.Matches(serverHtml);
foreach (Match m in jsMatches)
{
string src = m.Groups[1].Value;
if (string.IsNullOrWhiteSpace(src)) continue;
if (!_pluginManager.TryClassifyServerAsset(src, out var kind) || kind != ServerAssetKind.PluginAsset)
continue;
await CacheJsAsync(serverUrl, src, pluginCacheDir, jsBuilder);
}
}
private async Task CacheCssAsync(string serverUrl, string href, string pluginCacheDir, StringBuilder cssBuilder)
{
try
{
var uri = GetAbsoluteUri(serverUrl, href);
var fileName = Path.GetFileName(uri.AbsolutePath);
if (!fileName.EndsWith(".css", StringComparison.OrdinalIgnoreCase))
return;
var localPath = Path.Combine(pluginCacheDir, fileName);
var bytes = await _httpClient.GetByteArrayAsync(uri);
await File.WriteAllBytesAsync(localPath, bytes);
var outHref = $"plugin_cache/{fileName}";
AppendStyleOnce(cssBuilder, outHref);
}
catch (Exception ex)
{
Trace.WriteLine($"⚠ Failed to cache CSS '{href}': {ex}");
}
}
private async Task CacheJsAsync(string serverUrl, string src, string pluginCacheDir, StringBuilder jsBuilder)
{
try
{
// Server-relative or absolute; cache into plugin_cache root with filename
var uri = GetAbsoluteUri(serverUrl, src);
var fileName = Path.GetFileName(uri.AbsolutePath);
if (!fileName.EndsWith(".js", StringComparison.OrdinalIgnoreCase))
fileName += ".js";
var localPath = Path.Combine(pluginCacheDir, fileName);
var jsContent = await _httpClient.GetStringAsync(uri);
jsContent = await EsbuildHelper.TranspileAsync(jsContent, uri.ToString());
await File.WriteAllTextAsync(localPath, jsContent);
var outSrc = $"plugin_cache/{fileName}";
AppendScriptOnce(jsBuilder, $"<script src=\"{outSrc}\"></script>", outSrc);
}
catch (Exception ex)
{
Trace.WriteLine($"⚠ Failed to cache JS '{src}': {ex}");
}
}
private async Task ApplyApiInstalledPluginsAsync(
PackageWorkspace workspace,
string serverUrl,
string pluginCacheDir,
StringBuilder cssBuilder,
StringBuilder headJsBuilder,
StringBuilder bodyJsBuilder)
{
var apiPlugins = await _apiClient.GetInstalledPluginsAsync(serverUrl);
foreach (var plugin in apiPlugins)
{
var entry = _pluginManager.FindPluginEntry(plugin);
if (entry == null) continue;
var patch = _pluginManager.ResolvePatch(entry);
if (patch == null)
{
Trace.WriteLine($" No patch implementation registered for plugin '{entry.Name}', skipping.");
continue;
}
Trace.WriteLine($"⚙ Applying plugin patch: {entry.Name}");
var ctx = new PluginPatchContext(
workspace: workspace,
serverUrl: serverUrl,
pluginCacheDir: pluginCacheDir,
matrixEntry: entry,
pluginManager: _pluginManager,
cssBuilder: cssBuilder,
headJsBuilder: headJsBuilder,
bodyJsBuilder: bodyJsBuilder,
injectedScripts: _injectedScripts,
injectedStyles: _injectedStyles
);
await patch.ApplyAsync(ctx);
}
}
private static Uri GetAbsoluteUri(string serverUrl, string relOrAbs)
{
return UrlHelper.GetAbsoluteUri(serverUrl, relOrAbs);
}
private void AppendScriptOnce(StringBuilder js, string scriptTag, string src)
{
if (_injectedScripts.Add(src))
js.AppendLine(scriptTag);
else
Trace.WriteLine($" Script already injected, skipping: {src}");
}
private void AppendStyleOnce(StringBuilder css, string href)
{
if (_injectedStyles.Add(href))
css.AppendLine($"<link rel=\"stylesheet\" href=\"{href}\" />");
else
Trace.WriteLine($" CSS already injected, skipping: {href}");
}
}
}

View File

@@ -0,0 +1,250 @@
using Jellyfin2Samsung.Helpers.Core;
using Jellyfin2Samsung.Interfaces;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Jellyfin2Samsung.Helpers.Jellyfin.Plugins.KefinTweaks
{
public sealed class KefinTweaksPatch : IJellyfinPluginPatch
{
private const string KefinTweaksRawRoot =
"https://raw.githubusercontent.com/ranaldsgift/KefinTweaks/v0.4.5/";
public async Task ApplyAsync(PluginPatchContext ctx)
{
string pluginCacheDir = ctx.PluginCacheDir;
string serverUrl = ctx.ServerUrl;
string publicJsPath = Path.Combine(pluginCacheDir, "public.js");
if (!File.Exists(publicJsPath))
{
Trace.WriteLine("▶ KefinTweaks: public.js not found, skipping.");
return;
}
string js = await File.ReadAllTextAsync(publicJsPath, Encoding.UTF8);
if (!RegexPatterns.KefinTweaks.Loader.IsMatch(js))
{
Trace.WriteLine("▶ KefinTweaks: loader not found, skipping.");
return;
}
Trace.WriteLine("⚙ KefinTweaks: patching public.js");
Directory.CreateDirectory(Path.Combine(pluginCacheDir, "kefinTweaks"));
// --- main plugin ---
await ctx.PluginManager.DownloadAndTranspileAsync(
KefinTweaksRawRoot + "kefinTweaks-plugin.js",
pluginCacheDir,
Path.Combine("kefinTweaks", "kefinTweaks-plugin.js"));
RewritePluginRoot(pluginCacheDir);
// --- injector ---
var injectorPath = await ctx.PluginManager.DownloadAndTranspileAsync(
KefinTweaksRawRoot + "injector.js",
pluginCacheDir,
Path.Combine("kefinTweaks", "injector.js"));
if (injectorPath != null)
{
await ProcessModulesAsync(ctx, injectorPath);
await ProcessCssAsync(ctx);
js = await InjectSkinCssAsync(ctx, js);
}
// --- rewrite loader ---
js = RegexPatterns.KefinTweaks.Loader.Replace(
js,
"script.src = 'plugin_cache/kefinTweaks/kefinTweaks-plugin.js';");
js = RegexPatterns.KefinTweaks.TweaksRoot.Replace(
js,
@"""kefinTweaksRoot"": ""plugin_cache/kefinTweaks/""");
await File.WriteAllTextAsync(publicJsPath, js, Encoding.UTF8);
Trace.WriteLine("✓ KefinTweaks patched successfully");
}
// ------------------------------------------------------------
// Core helpers
// ------------------------------------------------------------
private static void RewritePluginRoot(string pluginCacheDir)
{
string path = Path.Combine(pluginCacheDir, "kefinTweaks", "kefinTweaks-plugin.js");
if (!File.Exists(path)) return;
var js = File.ReadAllText(path);
js = js.Replace(
"https://cdn.jsdelivr.net/gh/ranaldsgift/KefinTweaks",
"plugin_cache/kefinTweaks");
File.WriteAllText(path, js);
}
private async Task ProcessModulesAsync(PluginPatchContext ctx, string injectorPath)
{
string src = await File.ReadAllTextAsync(injectorPath, Encoding.UTF8);
var matches = RegexPatterns.KefinTweaks.ScriptEntry.Matches(src);
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (Match m in matches)
{
string name = m.Groups[1].Value.Trim();
if (string.IsNullOrEmpty(name)) continue;
if (!name.EndsWith(".js", StringComparison.OrdinalIgnoreCase)) continue;
if (!seen.Add(name)) continue;
if (name is "kefinTweaks-plugin.js" or "injector.js") continue;
string modulePath =
name.Equals("jquery.flurry.min.js", StringComparison.OrdinalIgnoreCase)
? "scripts/third%20party/" + name
: "scripts/" + name;
string url = KefinTweaksRawRoot + modulePath;
string relPath = Path.Combine(
"kefinTweaks",
modulePath.Replace("%20", " ")
.Replace("/", Path.DirectorySeparatorChar.ToString()));
await ctx.PluginManager.DownloadAndTranspileAsync(url, ctx.PluginCacheDir, relPath);
}
Trace.WriteLine("✓ KefinTweaks modules cached");
}
private async Task ProcessCssAsync(PluginPatchContext ctx)
{
var cssFiles = new[]
{
"chromic-kefin.css",
"elegant-kefin.css",
"fin-kefin-10.11.css",
"flow-kefin.css",
"glassfin-kefin.css",
"jamfin-kefin-10.css",
"jamfin-kefin.css",
"neutralfin-kefin.css",
"scyfin-kefin.css",
"optional/ElegantFin/solidAppBar.css",
"optional/ElegantFin/libraryLabelVisibility.css",
"optional/ElegantFin/extraOverlayButtons.css",
"optional/ElegantFin/centerPlayButton.css",
"optional/ElegantFin/cardHoverEffect.css",
};
Trace.WriteLine("▶ KefinTweaks: caching CSS skins");
foreach (var file in cssFiles)
{
try
{
string url = KefinTweaksRawRoot + "skins/" + file;
string local = Path.Combine(
ctx.PluginCacheDir,
"kefinTweaks",
"skins",
"css",
file.Replace("/", Path.DirectorySeparatorChar.ToString()));
Directory.CreateDirectory(Path.GetDirectoryName(local)!);
var bytes = await ctx.PluginManager.DownloadBytesAsync(url);
if (bytes != null)
await File.WriteAllBytesAsync(local, bytes);
}
catch (Exception ex)
{
Trace.WriteLine($"⚠ KefinTweaks CSS failed: {file} {ex}");
}
}
}
private async Task<string> InjectSkinCssAsync(PluginPatchContext ctx, string js)
{
var info = await ctx.PluginManager.Api.GetPublicSystemInfoAsync(ctx.ServerUrl);
var version = info?.Version ?? "0.0.0";
var majorMinor = string.Join(".", version.Split('.').Take(2));
var skin = GetKefinDefaultSkin(ctx.PluginCacheDir);
if (string.IsNullOrWhiteSpace(skin))
return js;
string skinLower = skin.ToLowerInvariant();
js = EnsureCssLinked(js,
$"plugin_cache/kefinTweaks/skins/css/{skinLower}-kefin.css");
js = EnsureCssLinked(js,
$"plugin_cache/kefinTweaks/skins/css/{skinLower}-kefin-{majorMinor}.css");
Trace.WriteLine($"✓ KefinTweaks skin injected: {skin} ({majorMinor})");
return js;
}
// ------------------------------------------------------------
// Utility helpers (verbatim behavior)
// ------------------------------------------------------------
private static string EnsureCssLinked(string js, string href)
{
if (js.Contains(href, StringComparison.OrdinalIgnoreCase))
return js;
return js + $@"
(function () {{
try {{
var href = '{href}';
if (document.querySelector('link[href=""' + href + '""]')) return;
var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = href;
document.head.appendChild(link);
console.log('🪼 KefinTweaks CSS injected:', href);
}} catch (e) {{
console.error('Failed to inject KefinTweaks CSS', e);
}}
}})();
";
}
private static string? GetKefinDefaultSkin(string pluginCacheDir)
{
try
{
string configPath = Path.Combine(
pluginCacheDir,
"kefinTweaks",
"config",
"config.json");
if (!File.Exists(configPath))
return null;
var json = File.ReadAllText(configPath);
var match = RegexPatterns.PluginConfig.DefaultSkin.Match(json);
return match.Success ? match.Groups[1].Value : null;
}
catch
{
return null;
}
}
}
}

View File

@@ -0,0 +1,165 @@
using Jellyfin2Samsung.Helpers.API;
using Jellyfin2Samsung.Helpers.Core;
using Jellyfin2Samsung.Helpers.Jellyfin.Plugins.KefinTweaks;
using Jellyfin2Samsung.Interfaces;
using Jellyfin2Samsung.Models;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace Jellyfin2Samsung.Helpers.Jellyfin.Plugins
{
/// <summary>
/// Responsible for:
/// - Mapping installed plugins -> PluginMatrixEntry
/// - Resolving plugin patch implementations (per-plugin behavior)
/// - Download/transpile helpers used by patches
/// </summary>
public sealed class PluginManager
{
private readonly HttpClient _httpClient;
private readonly JellyfinApiClient _apiClient;
public PluginManager(HttpClient httpClient, JellyfinApiClient apiClient)
{
_httpClient = httpClient;
_apiClient = apiClient;
}
public PluginMatrixEntry? FindPluginEntry(JellyfinPluginInfo plugin)
{
if (plugin?.Name == null) return null;
string pluginName = plugin.Name.ToLowerInvariant();
return PluginMatrix.Matrix.FirstOrDefault(entry =>
pluginName.Contains(entry.Name, StringComparison.InvariantCultureIgnoreCase));
}
public IJellyfinPluginPatch? ResolvePatch(PluginMatrixEntry entry)
{
return entry.Name switch
{
"EditorsChoice" => new EditorsChoicePatch(),
"Jellyfin Enhanced" => new JellyfinEnhancedPatch(),
"Media Bar" => new MediaBarPatch(),
"Home Screen Sections" => new HomeScreenSectionsPatch(),
"KefinTweaks" => new KefinTweaksPatch(),
_ => null
};
}
public bool TryClassifyServerAsset(string url, out ServerAssetKind kind)
{
kind = ServerAssetKind.Unknown;
foreach (var rule in PluginMatrix.ServerAssetRules)
{
if (rule.match(url))
{
kind = rule.treatAs;
return true;
}
}
return false;
}
public async Task DownloadExplicitFilesAsync(string serverUrl, string pluginCacheDir, PluginMatrixEntry entry)
{
if (entry?.ExplicitServerFiles == null || entry.ExplicitServerFiles.Count == 0)
return;
Trace.WriteLine("▶ Downloading explicit Enhanced JS modules...");
foreach (var rel in entry.ExplicitServerFiles)
{
try
{
string url = UrlHelper.CombineUrl(serverUrl, rel);
Trace.WriteLine($" → Fetch: {url}");
string js = await _httpClient.GetStringAsync(url);
// Transpile to es2015 using esbuild; fallback is original JS.
js = await EsbuildHelper.TranspileAsync(js, rel);
string relPath = rel.TrimStart('/');
string outPath = Path.Combine(pluginCacheDir, relPath.Replace('/', Path.DirectorySeparatorChar));
string? directory = Path.GetDirectoryName(outPath);
if (directory != null)
Directory.CreateDirectory(directory);
if (!Path.HasExtension(outPath) || !outPath.EndsWith(".js", StringComparison.OrdinalIgnoreCase))
outPath += ".js";
await File.WriteAllTextAsync(outPath, js, Encoding.UTF8);
string logPath = outPath.Replace(pluginCacheDir + Path.DirectorySeparatorChar, "plugin_cache/");
Trace.WriteLine($" ✓ Saved {logPath}");
}
catch (Exception ex)
{
Trace.WriteLine($" ⚠ Failed Enhanced JS '{rel}': {ex}");
}
}
}
public async Task<string?> DownloadAndTranspileAsync(string url, string cacheDir, string relPath)
{
try
{
Trace.WriteLine($"▶ Downloading plugin JS: {url}");
string js = await _httpClient.GetStringAsync(url);
js = await EsbuildHelper.TranspileAsync(js, relPath);
string localPath = Path.Combine(cacheDir, relPath);
Directory.CreateDirectory(Path.GetDirectoryName(localPath)!);
await File.WriteAllTextAsync(localPath, js, Encoding.UTF8);
return localPath;
}
catch (Exception ex)
{
Trace.WriteLine($"⚠ Plugin download failed: {ex}");
return null;
}
}
public async Task<byte[]?> DownloadBytesAsync(string url)
{
try
{
return await _httpClient.GetByteArrayAsync(url);
}
catch (Exception ex)
{
Trace.WriteLine($"⚠ DownloadBytes download failed: {url} {ex}");
return null;
}
}
public async Task<string?> DownloadStringAsync(string url)
{
try
{
return await _httpClient.GetStringAsync(url);
}
catch (Exception ex)
{
Trace.WriteLine($"⚠ DownloadString failed: {url} {ex}");
return null;
}
}
public Task<JellyfinPublicSystemInfo?> GetPublicSystemInfoAsync(string serverUrl)
=> _apiClient.GetPublicSystemInfoAsync(serverUrl);
public JellyfinApiClient Api => _apiClient;
}
}

View File

@@ -0,0 +1,104 @@
using Jellyfin2Samsung.Models;
using System;
using System.Collections.Generic;
namespace Jellyfin2Samsung.Helpers.Jellyfin.Plugins
{
public static class PluginMatrix
{
public static readonly List<PluginMatrixEntry> Matrix =
[
new PluginMatrixEntry
{
Name = "Jellyfin Enhanced",
FallbackUrls = new(),
ExplicitServerFiles = new List<string>
{
"/JellyfinEnhanced/script",
"/JellyfinEnhanced/js/splashscreen.js",
"/JellyfinEnhanced/js/reviews.js",
"/JellyfinEnhanced/js/qualitytags.js",
"/JellyfinEnhanced/js/plugin.js",
"/JellyfinEnhanced/js/pausescreen.js",
"/JellyfinEnhanced/js/migrate.js",
"/JellyfinEnhanced/js/letterboxd-links.js",
"/JellyfinEnhanced/js/languagetags.js",
"/JellyfinEnhanced/js/genretags.js",
"/JellyfinEnhanced/js/elsewhere.js",
"/JellyfinEnhanced/js/arr-tag-links.js",
"/JellyfinEnhanced/js/arr-links.js",
"/JellyfinEnhanced/js/enhanced/config.js",
"/JellyfinEnhanced/js/enhanced/events.js",
"/JellyfinEnhanced/js/enhanced/features.js",
"/JellyfinEnhanced/js/enhanced/helpers.js",
"/JellyfinEnhanced/js/enhanced/playback.js",
"/JellyfinEnhanced/js/enhanced/subtitles.js",
"/JellyfinEnhanced/js/enhanced/themer.js",
"/JellyfinEnhanced/js/enhanced/ui.js",
"/JellyfinEnhanced/js/jellyseerr/api.js",
"/JellyfinEnhanced/js/jellyseerr/jellyseerr.js",
"/JellyfinEnhanced/js/jellyseerr/modal.js",
"/JellyfinEnhanced/js/jellyseerr/ui.js"
}
},
new PluginMatrixEntry
{
Name = "Media Bar",
FallbackUrls = new List<string>
{
"https://cdn.jsdelivr.net/gh/IAmParadox27/jellyfin-plugin-media-bar@main/slideshowpure.js"
},
UseBabel = true
},
new PluginMatrixEntry
{
Name = "EditorsChoice",
FallbackUrls = new List<string>
{
"https://raw.githubusercontent.com/lachlandcp/jellyfin-editors-choice-plugin/refs/heads/main/EditorsChoicePlugin/Api/client.js"
},
UseBabel = false
},
new PluginMatrixEntry
{
Name = "Home Screen Sections",
FallbackUrls = new List<string>
{
"https://raw.githubusercontent.com/IAmParadox27/jellyfin-plugin-home-sections/main/src/Jellyfin.Plugin.HomeScreenSections/Inject/HomeScreenSections.js"
},
UseBabel = true
},
new PluginMatrixEntry
{
Name = "Plugin Pages",
FallbackUrls = new(),
UseBabel = true
},
new PluginMatrixEntry
{
Name = "KefinTweaks",
FallbackUrls = new List<string>
{
"https://cdn.jsdelivr.net/gh/ranaldsgift/KefinTweaks@latest/kefinTweaks-plugin.js"
},
RawRoot = "https://raw.githubusercontent.com/ranaldsgift/KefinTweaks/v0.4.5/",
UseBabel = true
}
];
public static readonly List<ServerAssetRule> ServerAssetRules =
[
new ServerAssetRule(
pluginName: "GenericPluginAsset",
match: url => url.Contains("/plugins/", StringComparison.OrdinalIgnoreCase),
treatAs: ServerAssetKind.PluginAsset),
new ServerAssetRule("EditorsChoice", url => url.Contains("editorschoice", StringComparison.OrdinalIgnoreCase), ServerAssetKind.PluginAsset),
new ServerAssetRule("KefinTweaks", url => url.Contains("kefin", StringComparison.OrdinalIgnoreCase), ServerAssetKind.PluginAsset),
new ServerAssetRule("Media Bar", url => url.Contains("mediabar", StringComparison.OrdinalIgnoreCase), ServerAssetKind.PluginAsset),
new ServerAssetRule("Home Screen Sections", url => url.Contains("homescreensections", StringComparison.OrdinalIgnoreCase), ServerAssetKind.PluginAsset),
new ServerAssetRule("Jellyfin Enhanced", url => url.Contains("jellyfinenhanced", StringComparison.OrdinalIgnoreCase) || url.Contains("/JellyfinEnhanced/", StringComparison.OrdinalIgnoreCase), ServerAssetKind.PluginAsset),
];
public static List<PluginMatrixEntry> GetMatrix() => Matrix;
}
}

View File

@@ -0,0 +1,270 @@
using Jellyfin2Samsung.Helpers.Core;
using Jellyfin2Samsung.Helpers.Jellyfin.Plugins.EditorsChoice;
using Jellyfin2Samsung.Interfaces;
using Jellyfin2Samsung.Models;
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
namespace Jellyfin2Samsung.Helpers.Jellyfin.Plugins
{
public sealed class PluginPatchContext
{
public PluginPatchContext(
PackageWorkspace workspace,
string serverUrl,
string pluginCacheDir,
PluginMatrixEntry matrixEntry,
PluginManager pluginManager,
StringBuilder cssBuilder,
StringBuilder headJsBuilder,
StringBuilder bodyJsBuilder,
System.Collections.Generic.HashSet<string> injectedScripts,
System.Collections.Generic.HashSet<string> injectedStyles)
{
Workspace = workspace;
ServerUrl = serverUrl;
PluginCacheDir = pluginCacheDir;
MatrixEntry = matrixEntry;
PluginManager = pluginManager;
CssBuilder = cssBuilder;
HeadJsBuilder = headJsBuilder;
BodyJsBuilder = bodyJsBuilder;
InjectedScripts = injectedScripts;
InjectedStyles = injectedStyles;
}
public PackageWorkspace Workspace { get; }
public string ServerUrl { get; }
public string PluginCacheDir { get; }
public PluginMatrixEntry MatrixEntry { get; }
public PluginManager PluginManager { get; }
public StringBuilder CssBuilder { get; }
public StringBuilder HeadJsBuilder { get; }
public StringBuilder BodyJsBuilder { get; }
// For dedup / consistent injection
public System.Collections.Generic.HashSet<string> InjectedScripts { get; }
public System.Collections.Generic.HashSet<string> InjectedStyles { get; }
public void InjectScript(string scriptTag, string src)
{
if (InjectedScripts.Add(src))
BodyJsBuilder.AppendLine(scriptTag);
}
public void InjectStyle(string href)
{
if (InjectedStyles.Add(href))
CssBuilder.AppendLine($"<link rel=\"stylesheet\" href=\"{href}\" />");
}
}
// ------------------------
// EditorsChoice
// ------------------------
public sealed class EditorsChoicePatch : IJellyfinPluginPatch
{
public async Task ApplyAsync(PluginPatchContext ctx)
{
var entry = ctx.MatrixEntry;
foreach (var url in entry.FallbackUrls)
{
string cleanName = RegexPatterns.PluginName.NonAlphanumeric.Replace(entry.Name.ToLowerInvariant(), "");
string fileName = Path.GetFileName(new Uri(url).AbsolutePath);
string relPath = Path.Combine(cleanName, fileName);
var local = await ctx.PluginManager.DownloadAndTranspileAsync(url, ctx.PluginCacheDir, relPath);
if (local == null) continue;
// Apply EditorsChoice patch (your existing patch class)
var js = await File.ReadAllTextAsync(local, Encoding.UTF8);
js = new PatchEditorsChoice().ApplyAsync(js);
await File.WriteAllTextAsync(local, js, Encoding.UTF8);
string injectedSrc = $"plugin_cache/{cleanName}/{fileName}";
ctx.InjectScript($"<script defer src=\"{injectedSrc}\"></script>", injectedSrc);
break;
}
}
}
// ------------------------
// Jellyfin Enhanced (ExplicitServerFiles)
// ------------------------
public sealed class JellyfinEnhancedPatch : IJellyfinPluginPatch
{
public async Task ApplyAsync(PluginPatchContext ctx)
{
var entry = ctx.MatrixEntry;
// Download all explicit server files into plugin_cache preserving structure
await ctx.PluginManager.DownloadExplicitFilesAsync(ctx.ServerUrl, ctx.PluginCacheDir, entry);
// Inject main script last
// Your explicit list contains "/JellyfinEnhanced/script" -> stored as plugin_cache/JellyfinEnhanced/script.js
var main = Path.Combine(ctx.PluginCacheDir, "JellyfinEnhanced", "script.js");
if (File.Exists(main))
{
await PatchEnhancedMainScript(main);
var outSrc = "plugin_cache/JellyfinEnhanced/script.js";
ctx.InjectScript($"<script src=\"{outSrc}\"></script>", outSrc);
}
}
private static async Task PatchEnhancedMainScript(string scriptPath)
{
string original = await File.ReadAllTextAsync(scriptPath);
string patch = @"
// ---- J2S SCRIPT PATCH: FORCE LOCAL ENHANCED MODULE LOADING ----
(function () {
function rewriteEnhancedUrl(url) {
try {
if (typeof url !== 'string') return url;
var base = url.split('?')[0];
if (base.endsWith('.js') && base.indexOf('/JellyfinEnhanced/') !== -1) {
var idx = base.indexOf('/JellyfinEnhanced/');
var sub = base.substring(idx + '/JellyfinEnhanced/'.length);
return 'plugin_cache/JellyfinEnhanced/' + sub;
}
return url;
}
catch (e) {
console.error('J2S rewriteEnhancedUrl failed', e);
return url;
}
}
var _createElement = document.createElement;
document.createElement = function (tag) {
var el = _createElement.call(document, tag);
if (tag && tag.toLowerCase() === 'script') {
var _setAttribute = el.setAttribute;
el.setAttribute = function (name, value) {
if (name === 'src') {
value = rewriteEnhancedUrl(value);
}
return _setAttribute.call(el, name, value);
};
Object.defineProperty(el, 'src', {
configurable: true,
get: function () { return el.getAttribute('src'); },
set: function (value) {
value = rewriteEnhancedUrl(value);
_setAttribute.call(el, 'src', value);
}
});
}
return el;
};
if (typeof window.fetch === 'function') {
var _fetch = window.fetch;
window.fetch = function (resource, init) {
try {
if (typeof resource === 'string') resource = rewriteEnhancedUrl(resource);
} catch (e) {
console.error('FETCH rewrite failed', e);
}
return _fetch.call(this, resource, init);
};
}
console.log('🪼 J2S: Enhanced loader patched to use plugin_cache for Enhanced JS modules');
})();
";
await File.WriteAllTextAsync(scriptPath, patch + "\n\n" + original);
}
}
// ------------------------
// Media Bar (example: CSS + fallback JS)
// ------------------------
public sealed class MediaBarPatch : IJellyfinPluginPatch
{
public async Task ApplyAsync(PluginPatchContext ctx)
{
// CSS
const string cssUrl = "https://cdn.jsdelivr.net/gh/IAmParadox27/jellyfin-plugin-media-bar@main/slideshowpure.css";
var bytes = await ctx.PluginManager.DownloadBytesAsync(cssUrl);
if (bytes != null)
{
var dir = Path.Combine(ctx.PluginCacheDir, "mediabar");
Directory.CreateDirectory(dir);
var local = Path.Combine(dir, "slideshowpure.css");
await File.WriteAllBytesAsync(local, bytes);
ctx.InjectStyle("plugin_cache/mediabar/slideshowpure.css");
}
// JS (from matrix fallback)
var entry = ctx.MatrixEntry;
foreach (var url in entry.FallbackUrls)
{
var clean = "mediabar";
var fileName = Path.GetFileName(new Uri(url).AbsolutePath);
var relPath = Path.Combine(clean, fileName);
var local = await ctx.PluginManager.DownloadAndTranspileAsync(url, ctx.PluginCacheDir, relPath);
if (local == null) continue;
var injectedSrc = $"plugin_cache/{clean}/{fileName}";
ctx.InjectScript($"<script src=\"{injectedSrc}\"></script>", injectedSrc);
break;
}
}
}
// ------------------------
// Home Screen Sections (CSS + fallback JS)
// ------------------------
public sealed class HomeScreenSectionsPatch : IJellyfinPluginPatch
{
public async Task ApplyAsync(PluginPatchContext ctx)
{
const string cssUrl =
"https://raw.githubusercontent.com/IAmParadox27/jellyfin-plugin-home-sections/main/src/Jellyfin.Plugin.HomeScreenSections/Inject/HomeScreenSections.css";
var bytes = await ctx.PluginManager.DownloadBytesAsync(cssUrl);
if (bytes != null)
{
var dir = Path.Combine(ctx.PluginCacheDir, "homescreensections");
Directory.CreateDirectory(dir);
var local = Path.Combine(dir, "HomeScreenSections.css");
await File.WriteAllBytesAsync(local, bytes);
ctx.InjectStyle("plugin_cache/homescreensections/HomeScreenSections.css");
}
var entry = ctx.MatrixEntry;
foreach (var url in entry.FallbackUrls)
{
var clean = "homescreensections";
var fileName = Path.GetFileName(new Uri(url).AbsolutePath);
var relPath = Path.Combine(clean, fileName);
var local = await ctx.PluginManager.DownloadAndTranspileAsync(url, ctx.PluginCacheDir, relPath);
if (local == null) continue;
var injectedSrc = $"plugin_cache/{clean}/{fileName}";
ctx.InjectScript($"<script src=\"{injectedSrc}\"></script>", injectedSrc);
break;
}
}
}
}

View File

@@ -1,322 +0,0 @@
using Jellyfin2SamsungCrossOS.Models;
using Jellyfin2SamsungCrossOS.Services;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
namespace Jellyfin2SamsungCrossOS.Helpers
{
public class JellyfinHelper
{
private readonly HttpClient _httpClient;
private readonly ProcessHelper _processHelper;
public JellyfinHelper(
HttpClient httpClient,
ProcessHelper processHelper)
{
_httpClient = httpClient;
_processHelper = processHelper;
}
public static bool IsValidJellyfinConfiguration()
{
return !string.IsNullOrEmpty(AppSettings.Default.JellyfinIP) &&
!string.IsNullOrEmpty(AppSettings.Default.JellyfinApiKey) &&
AppSettings.Default.JellyfinApiKey.Length == 32 &&
IsValidUrl($"http://{AppSettings.Default.JellyfinIP}/Users");
}
public static bool IsValidUrl(string url)
{
return Uri.TryCreate(url, UriKind.Absolute, out var uriResult)
&& (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps);
}
public async Task<List<JellyfinAuth>> LoadJellyfinUsersAsync()
{
var users = new List<JellyfinAuth>();
if (!IsValidJellyfinConfiguration())
{
Debug.WriteLine("Invalid Jellyfin configuration - skipping user load");
return users;
}
try
{
_httpClient.DefaultRequestHeaders.Clear();
_httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("SamsungJellyfinInstaller/1.0");
_httpClient.DefaultRequestHeaders.Add("Authorization", $"MediaBrowser Token=\"{AppSettings.Default.JellyfinApiKey}\"");
var url = $"http://{AppSettings.Default.JellyfinIP}/Users";
Debug.WriteLine($"Attempting to load users from: {url}");
using var response = await _httpClient.GetAsync(url);
if (response.IsSuccessStatusCode)
{
var json = await response.Content.ReadAsStringAsync();
var jellyfinUsers = JsonSerializer.Deserialize<List<JellyfinAuth>>(json, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
if (jellyfinUsers != null && jellyfinUsers.Any())
{
users.AddRange(jellyfinUsers);
if (users.Count > 1)
{
users.Add(new JellyfinAuth
{
Id = "everyone",
Name = "Everyone"
});
}
Debug.WriteLine($"Successfully loaded {users.Count} users");
}
else
{
Debug.WriteLine("No users found in response");
}
}
else
{
Debug.WriteLine($"Failed to load users - Status: {response.StatusCode}, Reason: {response.ReasonPhrase}");
}
}
catch (HttpRequestException ex)
{
Debug.WriteLine($"HTTP error loading Jellyfin users: {ex.Message}");
}
catch (TaskCanceledException ex)
{
Debug.WriteLine($"Request timeout loading Jellyfin users: {ex.Message}");
}
catch (JsonException ex)
{
Debug.WriteLine($"JSON parsing error: {ex.Message}");
}
catch (Exception ex)
{
Debug.WriteLine($"Unexpected error loading Jellyfin users: {ex.Message}");
}
return users;
}
public async Task<InstallResult> ApplyConfigAndResignPackageAsync(
string TizenCliPath,
string packagePath,
string certificateName,
string[] userIds)
{
string? tempDir = null;
string? tempPackage = null;
try
{
var baseDir = Path.GetDirectoryName(packagePath)
?? throw new InvalidOperationException("Invalid package path");
tempDir = Path.Combine(baseDir, $"JellyTemp_{Guid.NewGuid():N}");
Directory.CreateDirectory(tempDir);
ZipFile.ExtractToDirectory(packagePath, tempDir);
if (AppSettings.Default.ConfigUpdateMode.Contains("Server") ||
AppSettings.Default.ConfigUpdateMode.Contains("All"))
{
Debug.WriteLine(tempDir);
UpdateMultiServerConfig(tempDir);
}
if (AppSettings.Default.ConfigUpdateMode.Contains("Browser") ||
AppSettings.Default.ConfigUpdateMode.Contains("All"))
{
await InjectUserSettingsScriptAsync(tempDir, userIds);
}
tempPackage = Path.Combine(baseDir, $"{Path.GetFileNameWithoutExtension(packagePath)}_mod.wgt");
if (File.Exists(tempPackage)) File.Delete(tempPackage);
ZipFile.CreateFromDirectory(tempDir, tempPackage);
File.Delete(packagePath);
File.Move(tempPackage, packagePath);
if (OperatingSystem.IsWindows())
await _processHelper.RunCommandCmdAsync(TizenCliPath, $"sign --signing-profile {certificateName} \"{packagePath}\"");
else
await _processHelper.RunCommandAsync(TizenCliPath, $"sign --signing-profile {certificateName} \"{packagePath}\"");
return InstallResult.SuccessResult();
}
catch (Exception ex)
{
return InstallResult.FailureResult($"Error modifying Jellyfin package: {ex.Message}");
}
finally
{
if (Directory.Exists(tempDir))
Directory.Delete(tempDir, true);
if (tempPackage != null && File.Exists(tempPackage))
File.Delete(tempPackage);
}
}
public static async Task UpdateMultiServerConfig(string tempDirectory)
{
string configPath = Path.Combine(tempDirectory, "www", "config.json");
if (!File.Exists(configPath))
throw new FileNotFoundException("Jellyfin config.json not found", configPath);
string jsonText = await File.ReadAllTextAsync(configPath);
if (string.IsNullOrWhiteSpace(jsonText))
throw new InvalidDataException("config.json is empty or invalid");
var config = JsonNode.Parse(jsonText) ?? throw new JsonException("Failed to parse config.json");
config["multiserver"] = false;
string serverUrl = $"http://{AppSettings.Default.JellyfinIP}";
config["servers"] = new JsonArray { JsonValue.Create(serverUrl) };
var options = new JsonSerializerOptions { WriteIndented = true };
await File.WriteAllTextAsync(configPath, config.ToJsonString(options));
}
public static async Task InjectUserSettingsScriptAsync(string tempDirectory, string[] userIds)
{
if (userIds == null || userIds.Length == 0)
return;
string indexPath = Path.Combine(tempDirectory, "index.html");
if (!File.Exists(indexPath))
throw new FileNotFoundException("index.html not found", indexPath);
string htmlContent = await File.ReadAllTextAsync(indexPath);
const string injectionMarker = "<!-- SAMSUNG_JELLYFIN_AUTO_INJECTED -->";
if (htmlContent.Contains(injectionMarker))
return;
string BoolToJsString(bool value) => value.ToString().ToLower();
var scriptBuilder = new StringBuilder();
scriptBuilder.AppendLine(injectionMarker);
scriptBuilder.AppendLine("<script>");
scriptBuilder.AppendLine("(function() {");
foreach (string userId in userIds.Where(u => !string.IsNullOrWhiteSpace(u)))
{
scriptBuilder.AppendLine($" // Settings for user: {userId}");
scriptBuilder.AppendLine($" var userId = '{userId}';");
scriptBuilder.AppendLine();
scriptBuilder.AppendLine(" if (localStorage.getItem('samsung-jellyfin-injected-' + userId)) return;");
scriptBuilder.AppendLine();
scriptBuilder.AppendLine($" localStorage.setItem(userId + '-appTheme', '{AppSettings.Default.SelectedTheme ?? "dark"}');");
scriptBuilder.AppendLine($" localStorage.setItem(userId + '-enableBackdrops', '{BoolToJsString(AppSettings.Default.EnableBackdrops)}');");
scriptBuilder.AppendLine($" localStorage.setItem(userId + '-enableThemeSongs', '{BoolToJsString(AppSettings.Default.EnableThemeSongs)}');");
scriptBuilder.AppendLine($" localStorage.setItem(userId + '-enableThemeVideos', '{BoolToJsString(AppSettings.Default.EnableThemeVideos)}');");
scriptBuilder.AppendLine($" localStorage.setItem(userId + '-backdropScreensaver', '{BoolToJsString(AppSettings.Default.BackdropScreensaver)}');");
scriptBuilder.AppendLine($" localStorage.setItem(userId + '-detailsBanner', '{BoolToJsString(AppSettings.Default.DetailsBanner)}');");
scriptBuilder.AppendLine($" localStorage.setItem(userId + '-cinemaMode', '{BoolToJsString(AppSettings.Default.CinemaMode)}');");
scriptBuilder.AppendLine($" localStorage.setItem(userId + '-nextUpEnabled', '{BoolToJsString(AppSettings.Default.NextUpEnabled)}');");
scriptBuilder.AppendLine($" localStorage.setItem(userId + '-enableExternalVideoPlayers', '{BoolToJsString(AppSettings.Default.EnableExternalVideoPlayers)}');");
scriptBuilder.AppendLine($" localStorage.setItem(userId + '-skipIntros', '{BoolToJsString(AppSettings.Default.SkipIntros)}');");
scriptBuilder.AppendLine();
// Language preferences
if (!string.IsNullOrWhiteSpace(AppSettings.Default.AudioLanguagePreference))
scriptBuilder.AppendLine($" localStorage.setItem(userId + '-audioLanguagePreference', '{AppSettings.Default.AudioLanguagePreference}');");
if (!string.IsNullOrWhiteSpace(AppSettings.Default.SubtitleLanguagePreference))
scriptBuilder.AppendLine($" localStorage.setItem(userId + '-subtitleLanguagePreference', '{AppSettings.Default.SubtitleLanguagePreference}');");
// Mark injected
scriptBuilder.AppendLine(" localStorage.setItem('samsung-jellyfin-injected-' + userId, 'true');");
scriptBuilder.AppendLine();
}
scriptBuilder.AppendLine("})();");
scriptBuilder.AppendLine("</script>");
htmlContent = htmlContent.Replace("<head>", "<head>" + scriptBuilder.ToString());
await File.WriteAllTextAsync(indexPath, htmlContent);
}
public async Task UpdateJellyfinUsersAsync(string[] userIds)
{
if (userIds == null || userIds.Length == 0)
return;
try
{
_httpClient.DefaultRequestHeaders.Clear();
_httpClient.DefaultRequestHeaders.Add("Authorization", $"MediaBrowser Token=\"{AppSettings.Default.JellyfinApiKey}\"");
foreach (string userId in userIds.Where(u => !string.IsNullOrWhiteSpace(u)))
{
try
{
// Fetch user info
var getUserResponse = await _httpClient.GetAsync($"http://{AppSettings.Default.JellyfinIP}/Users/{userId}");
getUserResponse.EnsureSuccessStatusCode();
var userJson = await getUserResponse.Content.ReadAsStringAsync();
var userNode = JsonNode.Parse(userJson) ?? throw new JsonException("Failed to parse user JSON");
// Update auto-login setting
userNode["EnableAutoLogin"] = AppSettings.Default.UserAutoLogin;
var userContent = new StringContent(
userNode.ToJsonString(new JsonSerializerOptions { WriteIndented = true }),
Encoding.UTF8,
"application/json");
using (userContent)
{
var userResponse = await _httpClient.PostAsync($"http://{AppSettings.Default.JellyfinIP}/Users?userId={userId}", userContent);
userResponse.EnsureSuccessStatusCode();
}
// Update additional user configurations
var userConfig = new
{
PlayDefaultAudioTrack = AppSettings.Default.PlayDefaultAudioTrack,
SubtitleLanguagePreference = AppSettings.Default.SubtitleLanguagePreference,
SubtitleMode = AppSettings.Default.SelectedSubtitleMode,
RememberAudioSelections = AppSettings.Default.RememberAudioSelections,
RememberSubtitleSelections = AppSettings.Default.RememberSubtitleSelections,
EnableNextEpisodeAutoPlay = AppSettings.Default.AutoPlayNextEpisode,
};
var configJson = JsonSerializer.Serialize(userConfig, new JsonSerializerOptions { WriteIndented = true });
using var configContent = new StringContent(configJson, Encoding.UTF8, "application/json");
var configResponse = await _httpClient.PostAsync($"http://{AppSettings.Default.JellyfinIP}/Users/Configuration?userId={userId}", configContent);
configResponse.EnsureSuccessStatusCode();
}
catch (Exception userEx)
{
Debug.WriteLine($"Failed to update configuration for user {userId}: {userEx.Message}");
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"General error updating user configurations: {ex.Message}");
}
}
}
}

View File

@@ -1,31 +0,0 @@
using System;
using System.IO;
namespace Jellyfin2SamsungCrossOS.Helpers
{
public class OperatingSystemHelper
{
public string GetInstallPath()
{
string baseDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
if (OperatingSystem.IsWindows())
{
return Path.Combine(baseDir, "Programs", "TizenStudioCli");
}
else if (OperatingSystem.IsLinux())
{
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "TizenStudioCli");
}
else if (OperatingSystem.IsMacOS())
{
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Applications", "TizenStudioCli");
}
else
{
throw new PlatformNotSupportedException("Unsupported OS");
}
}
}
}

View File

@@ -1,240 +0,0 @@
using Jellyfin2SamsungCrossOS.Models;
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Jellyfin2SamsungCrossOS.Helpers
{
public class ProcessHelper
{
public static void KillSdbServers()
{
try
{
Process[] sdbProcesses = Process.GetProcessesByName("sdb");
if (sdbProcesses.Length == 0)
return;
foreach (Process proc in sdbProcesses)
{
proc.Kill();
proc.WaitForExit();
Debug.WriteLine($"Killed SDB {proc.Id} - {proc.ProcessName}");
}
}
catch (Exception ex)
{
Debug.WriteLine($"Failed to stop SDB server: {ex.Message}");
}
}
public Task<ProcessResult> RunCommandCmdAsync(string fileName, string arguments, string? workingDirectory = null)
{
var cmdArgs = $"/c \"\"{fileName}\" {arguments}\"";
return RunCommandAsync("cmd.exe", cmdArgs, workingDirectory);
}
public async Task<ProcessResult> RunCommandAsync(string fileName, string arguments, string? workingDirectory = null)
{
var tcs = new TaskCompletionSource<ProcessResult>();
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = fileName,
Arguments = arguments,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true,
WorkingDirectory = workingDirectory ?? ""
},
EnableRaisingEvents = true
};
var output = new StringBuilder();
process.OutputDataReceived += (_, e) => { if (e.Data != null) output.AppendLine(e.Data); };
process.ErrorDataReceived += (_, e) => { if (e.Data != null) output.AppendLine(e.Data); };
process.Exited += (_, _) =>
{
tcs.SetResult(new ProcessResult
{
ExitCode = process.ExitCode,
Output = output.ToString()
});
};
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
return await tcs.Task;
}
public async Task<ProcessResult?> RunElevatedAndCaptureOutputAsync(string filePath, string arguments, string workingDir)
{
if (string.IsNullOrEmpty(filePath))
throw new ArgumentException("filePath cannot be null or empty", nameof(filePath));
if (string.IsNullOrEmpty(workingDir))
workingDir = Environment.CurrentDirectory;
var tempFile = Path.Combine(Path.GetTempPath(), $"tizen_ext_{Guid.NewGuid():N}.txt");
try
{
ProcessStartInfo startInfo;
if (OperatingSystem.IsWindows())
{
var fullCommand = $"echo === Checking Tizen Packages activation status === && \"{filePath}\" {arguments} > \"{tempFile}\" 2>&1";
startInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = $"/c {fullCommand}",
WorkingDirectory = workingDir,
UseShellExecute = true,
Verb = "runas",
CreateNoWindow = false
};
}
else
{
startInfo = new ProcessStartInfo
{
FileName = filePath,
Arguments = arguments,
WorkingDirectory = workingDir,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
}
using var process = Process.Start(startInfo);
if (process == null)
return null;
string output;
if (OperatingSystem.IsWindows())
{
await process.WaitForExitAsync();
if (!File.Exists(tempFile))
return null;
output = await File.ReadAllTextAsync(tempFile);
File.Delete(tempFile);
}
else
{
var outputBuilder = new StringBuilder();
process.OutputDataReceived += (_, e) => { if (e.Data != null) outputBuilder.AppendLine(e.Data); };
process.ErrorDataReceived += (_, e) => { if (e.Data != null) outputBuilder.AppendLine(e.Data); };
process.BeginOutputReadLine();
process.BeginErrorReadLine();
await process.WaitForExitAsync();
output = outputBuilder.ToString();
}
return new ProcessResult
{
ExitCode = process.ExitCode,
Output = output
};
}
catch (Win32Exception ex) when (ex.NativeErrorCode == 1223)
{
return null; // user cancelled
}
catch
{
return null;
}
}
public async Task<ProcessResult> RunPrivilegedCommandAsync(string programPath, string[] arguments, string? workingDirectory = null)
{
if (OperatingSystem.IsWindows())
{
throw new PlatformNotSupportedException("RunPrivilegedCommandAsync only supports Linux/macOS currently.");
}
string fileName;
string args;
if (OperatingSystem.IsMacOS())
{
// Use osascript for macOS GUI privilege escalation
fileName = "osascript";
var command = $"{programPath} {string.Join(" ", arguments.Select(EscapeShellArgument))}";
args = $"-e \"do shell script \\\"{EscapeAppleScriptString(command)}\\\" with administrator privileges\"";
}
else if (OperatingSystem.IsLinux())
{
// Try pkexec first (GUI prompt), fallback to gksu/kdesu
var escalationTool = await GetLinuxPrivilegeEscalationTool();
fileName = escalationTool;
if (escalationTool == "pkexec")
{
args = $"{programPath} {string.Join(" ", arguments.Select(EscapeShellArgument))}";
}
else if (escalationTool == "gksu" || escalationTool == "kdesu")
{
var command = $"{programPath} {string.Join(" ", arguments.Select(EscapeShellArgument))}";
args = $"\"{command}\"";
}
else
{
throw new InvalidOperationException("No GUI privilege escalation tool found. Please install pkexec, gksu, or kdesu.");
}
}
else
{
throw new PlatformNotSupportedException("Unsupported OS.");
}
return await RunCommandAsync(fileName, args, workingDirectory);
}
private static string EscapeShellArgument(string arg)
{
if (string.IsNullOrEmpty(arg))
return "\"\"";
// Escape single quotes and wrap in single quotes for shell safety
return $"'{arg.Replace("'", "'\"'\"'")}'";
}
private static string EscapeAppleScriptString(string str)
{
// Escape quotes and backslashes for AppleScript
return str.Replace("\\", "\\\\").Replace("\"", "\\\"");
}
private async Task<string> GetLinuxPrivilegeEscalationTool()
{
// Check which GUI privilege escalation tools are available
var tools = new[] { "pkexec", "gksu", "kdesu" };
foreach (var tool in tools)
{
try
{
var result = await RunCommandAsync("which", tool);
if (result.ExitCode == 0)
return tool;
}
catch
{
// Continue to next tool
}
}
return "pkexec"; // Default fallback
}
}
}

View File

@@ -0,0 +1,145 @@
using Jellyfin2Samsung.Models;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net.Http;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text.Json;
using System.Threading.Tasks;
namespace Jellyfin2Samsung.Helpers.Tizen.Certificate
{
public class CertificateHelper
{
public List<ExistingCertificates> GetAvailableCertificates(string certificateFolders)
{
var certificates = new List<ExistingCertificates>();
var cipherUtil = new CipherUtil();
List<string> duids = new List<string>();
// Default item
certificates.Add(new ExistingCertificates
{
Name = "Jelly2Sams (default)",
Duid = string.Empty,
File = null,
ExpireDate = null
});
if (!Directory.Exists(certificateFolders))
return certificates;
var p12Files = Directory.GetFiles(
certificateFolders,
"author.p12",
SearchOption.AllDirectories);
foreach (var p12Path in p12Files)
{
var directory = Path.GetDirectoryName(p12Path);
if (directory == null)
continue;
var passwordPath = Path.Combine(directory, "password.txt");
if (!File.Exists(passwordPath))
continue;
var password = File.ReadAllText(passwordPath).Trim();
if (string.IsNullOrWhiteSpace(password))
continue;
try
{
var cert = new X509Certificate2(
p12Path,
password,
X509KeyStorageFlags.Exportable);
var dist = new X509Certificate2(
p12Path.Replace("author.p12", "distributor.p12"),
password,
X509KeyStorageFlags.Exportable);
// Extract DUID
string duid = "";
foreach (var ext in dist.Extensions)
{
if (ext.Oid?.Value == "2.5.29.17") // Subject Alternative Name
{
try
{
var asnData = new AsnEncodedData(ext.Oid, ext.RawData);
var formatted = asnData.Format(true);
var lines = formatted.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var line in lines)
{
if (line.Contains("URL=URN:tizen:deviceid=", StringComparison.OrdinalIgnoreCase))
{
var parts = line.Split('=');
if (parts.Length == 3)
{
duid = parts[2].Trim();
}
}
}
}
catch { }
}
}
if (cert.NotAfter.Date >= DateTime.Today)
{
certificates.Add(new ExistingCertificates
{
Name = cert.GetNameInfo(X509NameType.SimpleName, forIssuer: false),
File = p12Path,
ExpireDate = cert.NotAfter,
Duid = duid
});
}
}
catch (Exception ex)
{
Trace.WriteLine($"Failed to load certificate '{p12Path}': {ex}");
}
}
return certificates;
}
public async Task HandleErrorResponse(HttpResponseMessage response)
{
if (response.StatusCode == System.Net.HttpStatusCode.Forbidden)
{
throw new Exception("You've made too many requests in a given amount of time.\nPlease wait and try your request again later.");
}
try
{
var errorContent = await response.Content.ReadAsStringAsync();
if (!string.IsNullOrEmpty(errorContent))
{
var errorJson = JsonSerializer.Deserialize<JsonElement>(errorContent);
if (errorJson.TryGetProperty("error", out var errorObj))
{
var name = errorObj.TryGetProperty("name", out var nameEl) ? nameEl.ToString() : "";
var status = errorObj.TryGetProperty("status", out var statusEl) ? statusEl.ToString() : "";
var code = errorObj.TryGetProperty("code", out var codeEl) ? codeEl.ToString() : "";
var description = errorObj.TryGetProperty("description", out var descEl) ? descEl.ToString() : "";
throw new Exception($"Samsung API Error - Name: {name}, Status: {status}, Code: {code}, Description: {description}");
}
}
}
catch (JsonException)
{
}
throw new Exception($"Server response code: {response.StatusCode}");
}
}
}

View File

@@ -0,0 +1,64 @@
using System;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
namespace Jellyfin2Samsung.Helpers.Tizen.Certificate
{
public class CipherUtil
{
private const string KeyString = "KYANINYLhijklmnopqrstuvwx";
private byte[] KeyBytes => Encoding.UTF8.GetBytes(KeyString).Take(24).ToArray();
public string GetEncryptedString(string plainText)
{
using var tdes = new TripleDESCryptoServiceProvider
{
Key = KeyBytes,
Mode = CipherMode.ECB,
Padding = PaddingMode.PKCS7
};
var data = Encoding.UTF8.GetBytes(plainText);
byte[] encrypted = tdes.CreateEncryptor().TransformFinalBlock(data, 0, data.Length);
return Convert.ToBase64String(encrypted);
}
public string GetDecryptedString(string encryptedBase64)
{
byte[] encryptedBytes = Convert.FromBase64String(encryptedBase64);
using var tripleDes = TripleDES.Create();
tripleDes.Key = KeyBytes;
tripleDes.Mode = CipherMode.ECB;
tripleDes.Padding = PaddingMode.PKCS7;
byte[] decrypted = tripleDes.CreateDecryptor().TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length);
return Encoding.UTF8.GetString(decrypted);
}
public string GenerateRandomPassword(int length = 12)
{
if (length < 8)
throw new ArgumentException("Password must be at least 8 characters long.");
const string upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const string lower = "abcdefghijklmnopqrstuvwxyz";
const string digits = "0123456789";
const string all = upper + lower + digits;
var randomBytes = new byte[length];
using var rng = RandomNumberGenerator.Create();
rng.GetBytes(randomBytes);
var chars = new char[length];
chars[0] = upper[randomBytes[0] % upper.Length];
chars[1] = lower[randomBytes[1] % lower.Length];
chars[2] = digits[randomBytes[2] % digits.Length];
for (int i = 3; i < length; i++)
chars[i] = all[randomBytes[i] % all.Length];
return new string(chars.OrderBy(_ => Guid.NewGuid()).ToArray());
}
}
}

View File

@@ -0,0 +1,67 @@
using Jellyfin2Samsung.Helpers.API;
using Jellyfin2Samsung.Interfaces;
using Jellyfin2Samsung.Models;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace Jellyfin2Samsung.Helpers.Tizen.Devices
{
public class DeviceHelper
{
private readonly INetworkService _networkService;
private readonly TizenApiClient _tizenApiClient;
public DeviceHelper(
INetworkService networkService,
TizenApiClient tizenApiClient)
{
_networkService = networkService;
_tizenApiClient = tizenApiClient;
}
public async Task<List<NetworkDevice>> ScanForDevicesAsync(CancellationToken cancellationToken = default, bool virtualScan = false)
{
var devices = new List<NetworkDevice>();
var networkDevices = await _networkService.GetLocalTizenAddresses(cancellationToken, virtualScan);
foreach (NetworkDevice device in networkDevices)
{
// Check for cancellation before processing each device
cancellationToken.ThrowIfCancellationRequested();
if (await _networkService.IsPortOpenAsync(device.IpAddress, 8001, cancellationToken))
{
try
{
var samsungDevice = await _tizenApiClient.GetDeveloperInfoAsync(device);
if (!string.IsNullOrEmpty(samsungDevice.DeviceName))
devices.Add(samsungDevice);
}
catch
{
Trace.WriteLine($"Failed to get developer info for device at {device.IpAddress}.");
}
}
else
{
try
{
device.ModelName = device.ModelName;
device.Manufacturer = device.Manufacturer;
device.DeveloperMode = "1";
device.DeveloperIP = string.Empty;
devices.Add(device);
}
catch { }
}
}
return devices;
}
}
}

View File

@@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>CFBundleName</key>
<string>Jellyfin2SamsungCrossOS</string>
<string>Jellyfin2Samsung</string>
<key>CFBundleDisplayName</key>
<string>Jellyfin2Samsung</string>
<key>CFBundleIdentifier</key>
@@ -15,7 +15,7 @@
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleExecutable</key>
<string>Jellyfin2SamsungCrossOS</string>
<string>Jellyfin2Samsung</string>
<key>CFBundleIconFile</key>
<string>jelly2sams.icns</string>
<key>LSMinimumSystemVersion</key>

View File

@@ -1,7 +1,7 @@
using Avalonia.Controls;
using System.Threading.Tasks;
namespace Jellyfin2SamsungCrossOS.Services
namespace Jellyfin2Samsung.Interfaces
{
public interface IDialogService
{

View File

@@ -0,0 +1,12 @@
using Jellyfin2Samsung.Helpers.Jellyfin.Plugins;
using System.Threading.Tasks;
namespace Jellyfin2Samsung.Interfaces
{
public interface IJellyfinPluginPatch
{
//string PluginName { get; }
Task ApplyAsync(PluginPatchContext ctx);
}
}

View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
namespace Jellyfin2SamsungCrossOS.Services
namespace Jellyfin2Samsung.Interfaces
{
public interface ILocalizationService
{

View File

@@ -1,10 +1,10 @@
using Jellyfin2SamsungCrossOS.Models;
using Jellyfin2Samsung.Models;
using System.Collections.Generic;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
namespace Jellyfin2SamsungCrossOS.Services
namespace Jellyfin2Samsung.Interfaces
{
public interface INetworkService
{
@@ -14,5 +14,8 @@ namespace Jellyfin2SamsungCrossOS.Services
Task<bool> IsPortOpenAsync(string ip, int port, CancellationToken ct);
string GetLocalIPAddress();
string InvertIPAddress(string ipAddress);
Task<string?> GetManufacturerFromIp(string ipAddress);
bool IsDifferentSubnet(string ip1, string ip2);
Task<IReadOnlyList<NetworkInterfaceOption>> GetNetworkInterfaceOptionsAsync();
}
}

View File

@@ -0,0 +1,12 @@
using System;
namespace Jellyfin2Samsung.Interfaces
{
public interface IThemeService
{
bool IsDarkMode { get; }
event EventHandler<bool>? ThemeChanged;
void SetTheme(bool isDarkMode);
void ApplyTheme();
}
}

Some files were not shown because too many files have changed in this diff Show More