├── assets ├── amazon.png ├── f-droid.png ├── flathub.png ├── appstore-dark.png ├── github-mark.png ├── appstore-light.png ├── github-mark-white.png └── google-play-badge.png ├── go.mod ├── README.md ├── go.sum ├── README.template.md ├── generate.sh ├── structs.go ├── main.go └── clients.yaml /assets/amazon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-jellyfin/clients/HEAD/assets/amazon.png -------------------------------------------------------------------------------- /assets/f-droid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-jellyfin/clients/HEAD/assets/f-droid.png -------------------------------------------------------------------------------- /assets/flathub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-jellyfin/clients/HEAD/assets/flathub.png -------------------------------------------------------------------------------- /assets/appstore-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-jellyfin/clients/HEAD/assets/appstore-dark.png -------------------------------------------------------------------------------- /assets/github-mark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-jellyfin/clients/HEAD/assets/github-mark.png -------------------------------------------------------------------------------- /assets/appstore-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-jellyfin/clients/HEAD/assets/appstore-light.png -------------------------------------------------------------------------------- /assets/github-mark-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-jellyfin/clients/HEAD/assets/github-mark-white.png -------------------------------------------------------------------------------- /assets/google-play-badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-jellyfin/clients/HEAD/assets/google-play-badge.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/awesome-jellyfin/scripts 2 | 3 | go 1.18 4 | 5 | require gopkg.in/yaml.v3 v3.0.1 // indirect 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!NOTE] 2 | > Clients moved to [awesome-jellyfin/awesome-jellyfin](https://github.com/awesome-jellyfin/awesome-jellyfin/blob/main/CLIENTS.md) 3 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 2 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 3 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 4 | -------------------------------------------------------------------------------- /README.template.md: -------------------------------------------------------------------------------- 1 | # Awesome Jellyfin Clients 2 | 3 | > A List of Awesome Jellyfin Clients 4 | 5 | 9 | 10 | {{ CLIENTS }} 11 | 12 | 16 | 17 | # Contributing 18 | 19 | Edit `clients.yaml` 20 | -------------------------------------------------------------------------------- /generate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | CONTENT=$(go run *.go) 4 | if [[ $? -ne 0 ]] 5 | then 6 | echo "Error:" 7 | echo "$CONTENT" 8 | exit 1 9 | fi 10 | 11 | echo "" > README.md 12 | while read p; do 13 | if [[ $p == "{{ CLIENTS }}" ]] 14 | then 15 | echo "$CONTENT" >> README.md 16 | else 17 | echo "$p" >> README.md 18 | fi 19 | done < README.template.md -------------------------------------------------------------------------------- /structs.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | // client structs 6 | 7 | type Price struct { 8 | Free *bool `yaml:"free"` 9 | Paid *bool `yaml:"paid"` 10 | } 11 | 12 | type Hoster struct { 13 | Icon string `yaml:"icon"` 14 | IconURL string `yaml:"icon-url"` 15 | Text string `yaml:"text"` 16 | URL string `yaml:"url"` 17 | } 18 | 19 | type Client struct { 20 | Name string `yaml:"name"` 21 | Targets []string `yaml:"targets"` 22 | Types []string `yaml:"types"` 23 | Official *bool `yaml:"official"` 24 | Beta *bool `yaml:"beta"` 25 | Website string `yaml:"website"` 26 | OpenSourceURL string `yaml:"oss"` 27 | Price Price `yaml:"price"` 28 | Downloads []*Hoster `yaml:"downloads"` 29 | } 30 | 31 | // misc 32 | 33 | type icon struct { 34 | Light string `yaml:"light"` 35 | Dark string `yaml:"dark"` 36 | Single string `yaml:"single"` 37 | Text string `yaml:"text"` 38 | } 39 | 40 | func (i *icon) Markdown(url string) string { 41 | if (i.Dark != "") != (i.Light != "") { 42 | panic("use 'single' if only single icon URL available") 43 | } 44 | if i.Dark != "" { 45 | return fmt.Sprintf(` 46 | 47 | 48 | 49 | 50 | 51 | 52 | `, url, i.Light, i.Dark, i.Dark) 53 | } 54 | if i.Text != "" { 55 | return fmt.Sprintf("[%s](%s)", i.Text, url) 56 | } 57 | return fmt.Sprintf("[![img](%s)](%s)", i.Single, url) 58 | } 59 | 60 | type clientsConfig struct { 61 | Clients []*Client `yaml:"clients"` 62 | Targets []*struct { 63 | Key string `yaml:"key"` 64 | Display string `yaml:"display"` 65 | Has map[string]string `yaml:"has"` 66 | } `yaml:"targets"` 67 | Icons map[string]*icon `yaml:"icons"` 68 | } 69 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "gopkg.in/yaml.v3" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | const ( 11 | GoodTrue = "✅" 12 | BadTrue = "☑️" 13 | GoodFalse = "❎" 14 | BadFalse = "❌" 15 | TypeMusic = "🎵" 16 | Official = "🔹" 17 | Beta = "🛠️" 18 | JellyfinOrg = "https://github.com/jellyfin/" 19 | ) 20 | 21 | func Select[T any](expr bool, whenTrue, whenFalse T) T { 22 | if expr { 23 | return whenTrue 24 | } 25 | return whenFalse 26 | } 27 | 28 | func Ref[T any](what T) *T { 29 | return &what 30 | } 31 | 32 | func DerefDef[T any](what *T, defaultValue T) T { 33 | if what != nil { 34 | return *what 35 | } 36 | return defaultValue 37 | } 38 | 39 | func Deref[T any](what *T) T { 40 | if what != nil { 41 | return *what 42 | } 43 | var def T 44 | return def 45 | } 46 | 47 | func main() { 48 | data, err := os.ReadFile("clients.yaml") 49 | if err != nil { 50 | panic(err) 51 | } 52 | var config clientsConfig 53 | if err = yaml.Unmarshal(data, &config); err != nil { 54 | panic(err) 55 | } 56 | 57 | // target -> list of clients 58 | identifierClientMap := make(map[string][]*Client) 59 | for _, client := range config.Clients { 60 | for _, targetStr := range client.Targets { 61 | targetStr = strings.TrimSpace(strings.ToLower(targetStr)) 62 | identifierClientMap[targetStr] = append(identifierClientMap[targetStr], client) 63 | } 64 | } 65 | for _, target := range config.Targets { 66 | fmt.Printf("## %s\n\n", target.Display) 67 | for has, hasDisplay := range target.Has { 68 | fmt.Printf("### %s\n\n", hasDisplay) 69 | 70 | fmt.Println("| Name | OSS | Free | Paid | Downloads |") 71 | fmt.Println("|------|-----|------|------|-----------|") 72 | 73 | for _, client := range identifierClientMap[strings.TrimSpace(strings.ToLower(has))] { 74 | var ( 75 | name = client.Name 76 | websiteURL string 77 | oss, free, paid string 78 | downloads strings.Builder 79 | ) 80 | 81 | // if open-source and in the 'jellyfin' GitHub org, mark as official by default 82 | if client.Official == nil && strings.HasPrefix(client.OpenSourceURL, JellyfinOrg) { 83 | client.Official = Ref(true) 84 | } 85 | 86 | // make free the default price if open-source 87 | if client.Price.Free == nil && client.OpenSourceURL != "" { 88 | client.Price.Free = Ref(true) 89 | } 90 | 91 | /// Pledges 92 | // append official badge 93 | if Deref(client.Official) { 94 | name += " " + Official 95 | } 96 | // append beta badge 97 | if Deref(client.Beta) { 98 | name += " " + Beta 99 | } 100 | // append type badges 101 | for _, t := range client.Types { 102 | if t == "Music" { 103 | name += " " + TypeMusic 104 | } 105 | } 106 | 107 | free = Select(DerefDef(client.Price.Free, false), GoodTrue, BadFalse) 108 | paid = Select(DerefDef(client.Price.Paid, false), BadTrue, GoodFalse) 109 | oss = Select(client.OpenSourceURL != "", GoodTrue, BadFalse) 110 | 111 | if client.OpenSourceURL != "" { 112 | websiteURL = client.OpenSourceURL 113 | } 114 | if client.Website != "" { 115 | websiteURL = client.Website 116 | } 117 | 118 | for _, hoster := range client.Downloads { 119 | if downloads.Len() > 0 { 120 | downloads.WriteString(" ") 121 | } 122 | 123 | // Simple Text Download 124 | if hoster.Text != "" { 125 | downloads.WriteString(fmt.Sprintf("[%s](%s)", hoster.Text, hoster.URL)) 126 | continue 127 | } 128 | 129 | // Icon Download 130 | var downloadMarkdown string 131 | if hoster.Icon != "" { 132 | if icon, ok := config.Icons[hoster.Icon]; ok { 133 | downloadMarkdown = icon.Markdown(hoster.URL) 134 | } else { 135 | panic("cannot find icon " + hoster.Icon + " in config") 136 | } 137 | } else if hoster.IconURL != "" { 138 | downloadMarkdown = (&icon{Single: hoster.IconURL}).Markdown(hoster.URL) 139 | } else if hoster.Text != "" { 140 | downloadMarkdown = fmt.Sprintf("[%s](%s)", hoster.Text, hoster.URL) 141 | } else { 142 | panic("invalid download. specify either icon, icon-url or text") 143 | } 144 | 145 | downloads.WriteString(strings.ReplaceAll( 146 | strings.ReplaceAll(downloadMarkdown, "\n", ""), "\t", "")) 147 | } 148 | 149 | fmt.Printf("| [%s](%s) | %s | %s | %s | %s |\n", 150 | name, websiteURL, oss, free, paid, downloads.String()) 151 | } 152 | fmt.Println() 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /clients.yaml: -------------------------------------------------------------------------------- 1 | clients: 2 | - name: "Jellyfin Media Player" 3 | targets: [ Windows, Linux, macOS ] 4 | oss: https://github.com/jellyfin/jellyfin-media-player 5 | downloads: 6 | - icon: github 7 | url: https://github.com/jellyfin/jellyfin-media-player/releases 8 | 9 | - icon: flathub 10 | url: https://flathub.org/apps/details/com.github.iwalton3.jellyfin-media-player 11 | 12 | - name: "Swiftfin" 13 | targets: [ iOS, AppleTV ] 14 | oss: https://github.com/jellyfin/swiftfin 15 | official: false 16 | beta: true 17 | downloads: 18 | - icon: apple-appstore 19 | url: https://apps.apple.com/ca/app/swiftfin/id1604098728 20 | 21 | - name: "Jellyfin Vue" 22 | targets: [ Browser ] 23 | oss: https://github.com/jellyfin/jellyfin-vue 24 | official: false 25 | beta: true 26 | downloads: 27 | - text: "Browser" 28 | url: https://jf-vue.pages.dev/ 29 | 30 | - icon: docker-hub 31 | url: https://github.com/jellyfin/jellyfin-vue/pkgs/container/jellyfin-vue 32 | 33 | - name: "JellyCon" 34 | targets: [ Kodi ] 35 | oss: https://github.com/jellyfin/jellycon 36 | downloads: 37 | - text: "Installation Guide" 38 | url: https://github.com/jellyfin/jellycon#installation 39 | 40 | - name: "Jellyfin for Kodi" 41 | targets: [ Kodi ] 42 | oss: https://github.com/jellyfin/jellyfin-kodi 43 | downloads: 44 | - text: "Installation Guide" 45 | url: https://jellyfin.org/docs/general/clients/kodi 46 | 47 | - name: "Jellyfin for Android" 48 | targets: [ Android ] 49 | oss: https://github.com/jellyfin/jellyfin-android 50 | downloads: 51 | - icon: f-droid 52 | url: https://f-droid.org/en/packages/org.jellyfin.mobile/ 53 | 54 | - icon: amazon-appstore 55 | url: https://www.amazon.com/gp/aw/d/B081RFTTQ9 56 | 57 | - icon: google-playstore 58 | url: https://play.google.com/store/apps/details?id=org.jellyfin.mobile 59 | 60 | - name: "Jellyfin Mobile for iOS" 61 | targets: [ iOS ] 62 | oss: https://github.com/jellyfin/jellyfin-expo 63 | downloads: 64 | - icon: apple-appstore 65 | url: https://apps.apple.com/us/app/jellyfin-mobile/id1480192618?mt=8 66 | 67 | - name: "Jellyfin for Android TV" 68 | targets: [ AndroidTV ] 69 | oss: https://github.com/jellyfin/jellyfin-androidtv 70 | downloads: 71 | - icon: amazon-appstore 72 | url: https://www.amazon.com/gp/aw/d/B07TX7Z725 73 | 74 | - icon: google-playstore 75 | url: https://play.google.com/store/apps/details?id=org.jellyfin.androidtv 76 | 77 | - name: "Jellyfin for Roku" 78 | targets: [ Roku ] 79 | oss: https://github.com/jellyfin/jellyfin-roku 80 | downloads: 81 | - text: Channel Store 82 | url: https://channelstore.roku.com/details/592369/jellyfin 83 | 84 | - name: "Jellyfin for WebOS" 85 | targets: [ WebOS ] 86 | oss: https://github.com/jellyfin/jellyfin-webos 87 | downloads: 88 | - text: Content Store 89 | url: https://us.lgappstv.com/main/tvapp/detail?appId=1030579 90 | 91 | - name: "Findroid" 92 | targets: [ Android ] 93 | oss: https://github.com/jarnedemeulemeester/findroid 94 | downloads: 95 | - text: IzzyOnDroid 96 | url: https://apt.izzysoft.de/fdroid/index/apk/dev.jdtech.jellyfin 97 | 98 | - icon: google-playstore 99 | url: https://play.google.com/store/apps/details?id=dev.jdtech.jellyfin 100 | 101 | - name: "Sailfin" 102 | targets: [ SailfishOS ] 103 | oss: https://github.com/heartfin/harbour-sailfin 104 | downloads: 105 | - text: OpenRepos 106 | url: https://openrepos.net/content/ahappyhuman/sailfin 107 | 108 | - name: 'Infuse' 109 | targets: [ iOS, AppleTV, macOS ] 110 | website: https://firecore.com/infuse 111 | price: 112 | free: true 113 | paid: true 114 | downloads: 115 | - icon: apple-appstore 116 | url: https://apps.apple.com/app/id1136220934?mt=8 117 | 118 | - name: "sonixd" 119 | targets: [ Browser ] 120 | types: [ Music ] 121 | oss: https://github.com/jeffvli/sonixd 122 | downloads: 123 | - icon: github 124 | url: https://github.com/jeffvli/sonixd/releases 125 | 126 | - name: "Finamp" 127 | targets: [ Android ] 128 | types: [ Music ] 129 | oss: https://github.com/jmshrv/finamp 130 | downloads: 131 | - icon: google-playstore 132 | url: https://play.google.com/store/apps/details?id=com.unicornsonlsd.finamp 133 | 134 | - name: "MrMC" 135 | targets: [ AppleTV ] 136 | oss: https://github.com/MrMC/mrmc 137 | price: 138 | free: true 139 | paid: true 140 | downloads: 141 | - icon: amazon-appstore 142 | url: https://www.amazon.com/gp/product/B01ENT3I1Q/ref=mas_pm_mrmc 143 | 144 | - icon: apple-appstore 145 | url: https://apps.apple.com/us/app/mrmc/id1059536415 146 | 147 | - icon: google-playstore 148 | url: https://play.google.com/store/apps/details?id=tv.mrmc.mrmc 149 | 150 | # --- 151 | 152 | icons: 153 | github: 154 | light: "assets/github-mark-white.png" 155 | dark: "assets/github-mark.png" 156 | flathub: 157 | light: "assets/flathub.png" 158 | dark: "assets/flathub.png" 159 | apple-appstore: 160 | light: "assets/appstore-light.png" 161 | dark: "assets/appstore-dark.png" 162 | google-playstore: 163 | single: "assets/google-play-badge.png" 164 | docker-hub: 165 | text: "🐳" 166 | f-droid: 167 | single: "assets/f-droid.png" 168 | amazon-appstore: 169 | single: "assets/amazon.png" 170 | 171 | 172 | targets: 173 | - key: Browser 174 | display: "🌎 Browser-Based" # TODO: only 1-level heading 175 | has: 176 | Browser: Browser 177 | 178 | - key: Desktop 179 | display: "💻 Desktop" 180 | has: 181 | Windows: "Windows" 182 | macOS: "macOS" 183 | Linux: "Linux" 184 | SailfishOS: "Sailfish OS" 185 | 186 | - key: Mobile 187 | display: "📱 Mobile" 188 | has: 189 | iOS: "iOS" 190 | Android: "Android" 191 | 192 | - key: TV 193 | display: "📺 TV" 194 | has: 195 | AppleTV: "Apple TV" # TODO: order 196 | AndroidTV: "Android TV" 197 | Kodi: "Kodi" 198 | Roku: "Roku" 199 | WebOS: "webOS" 200 | --------------------------------------------------------------------------------