• Login
  • Register
  • Login Register
    Login
    Username/Email:
    Password:
    Or login with a social network below
  • Forum
  • Website
  • GitHub
  • Status
  • Translation
  • Features
  • Team
  • Rules
  • Help
  • Feeds
User Links
  • Login
  • Register
  • Login Register
    Login
    Username/Email:
    Password:
    Or login with a social network below

    Useful Links Forum Website GitHub Status Translation Features Team Rules Help Feeds
    Jellyfin Forum Development Plugin Development Collections by Folder

     
    • 0 Vote(s) - 0 Average

    Collections by Folder

    Generate Collections by Foldername
    Solas
    Offline

    Junior Member

    Posts: 4
    Threads: 1
    Joined: 2025 Oct
    Reputation: 0
    Country:Germany
    #1
    2025-10-05, 11:36 PM (This post was last modified: 2025-10-07, 09:02 AM by Solas. Edited 2 times in total. Edit Reason: problem solved )
    The plugin scans the specified folders and automatically creates collections based on the folder name.


    https://raw.githubusercontent.com/Solas7...ifest.json
    Syunic
    Offline

    Junior Member

    Posts: 6
    Threads: 2
    Joined: 2025 Aug
    Reputation: 1
    Country:Germany
    #2
    2025-10-25, 10:14 AM
    Does this work with the newest version of jellyfin? cant seem to get it to work.
    Solas
    Offline

    Junior Member

    Posts: 4
    Threads: 1
    Joined: 2025 Oct
    Reputation: 0
    Country:Germany
    #3
    2025-10-25, 01:30 PM
    I add an prerelease (v0.3.6) today.
    It should work. However, you have to first click on the puzzle piece and then on settings.
    That's why it's still a pre-release. I haven't found a suitable solution yet.Don't forget to check the box Group movies into collections in the dashboard, libraries, display

    Ich habe heute eine Prerelaese (v0.3.6) hochgeladen.
    Es sollte funktionieren. Allerdings muss man zuerst auf das Puzzleteil und dann auf Einstellungen klicken.
    Deshalb ist es noch eine Vorabversion. Ich habe noch keine passende Lösung gefunden. Vergiss nicht, im Dashboard, Bibliotheken, Anzeige das Kontrollkästchen „Filme in Sammlungen gruppieren“ zu aktivieren. So hast du die Filme einmal gruppiert als Ordner und einmal als Sammlung. Dann fliegen die nicht als einzelne Filme in der Bibliothek rum
    Solas
    Offline

    Junior Member

    Posts: 4
    Threads: 1
    Joined: 2025 Oct
    Reputation: 0
    Country:Germany
    #4
    3 hours ago
    Small gimmick for my collections:
     
    I'm also using JS-Injector. In collections, a circle is still displayed in the upper right corner showing the number of unwatched movies. The script displays a red circle in the upper left corner showing the number of movies in the collection. 
    Script was created with KI, fell free to change it.

             

    Just insert in Javascript Injection Plugin:

    Code:
    // == Jellyfin Film-Collection Badge Script (Series ausgeschlossen, Schauspieler ignoriert) ==
    (async function(){
      console.clear();
      console.log('Film-Collection Badge (Series ausgeschlossen) startet...');
      const SERVER_URL = 'http://XXX.XXX.XXX.XXX:8096'; // Jellyfin-Server-Adresse
      const CARD_SELECTOR = '.cardScalable';
      const BADGE_CLASS = 'collection-count-badge-js';
      const CACHE_TTL_MS = 5 * 60 * 1000;
      // --- Badge CSS ---
      if (!document.getElementById('collection-count-badge-js-style')) {
        const s = document.createElement('style');
        s.id = 'collection-count-badge-js-style';
        s.textContent = `
          .${BADGE_CLASS}{
            position:absolute;
            top:6px;
            left:6px;
            min-width:30px;
            height:30px;
            border-radius:50%;
            background:#ff3b30;
            color:#fff;
            display:flex;
            align-items:center;
            justify-content:center;
            font-weight:700;
            font-size:13px;
            z-index:99999;
          }
        `;
        document.head.appendChild(s);
      }
      const cache = new Map();
      // --- ID aus Karte extrahieren ---
      function extractIdFromCard(card){
        if (!card) return null;
        try {
          if (card.dataset && (card.dataset.itemid || card.dataset.id || card.dataset.folderid))
            return card.dataset.itemid || card.dataset.id || card.dataset.folderid;
          const a = card.querySelector('a[href*="details?id="]') || card.querySelector('a[href*="details"]');
          if (a){
            const href = a.getAttribute('href')||'';
            const q = href.split('?')[1] || href.split('#')[1] || '';
            return new URLSearchParams(q).get('id') || null;
          }
        } catch(e){}
        return null;
      }
      // --- Prüfen, ob Item Collection ist und wie viele Movies sie enthält ---
      async function fetchMovieCountForCollection(id, apiKey){
        if (!id) return 0;
        const cached = cache.get(id);
        if (cached && (Date.now() - cached.ts) < CACHE_TTL_MS) return cached.count;
        try {
          const detailsUrl = `${SERVER_URL}/Items/${encodeURIComponent(id)}`;
          const detRes = await fetch(detailsUrl, { headers: {'X-Emby-Token': apiKey} });
          if (!detRes.ok) { cache.set(id, {count:0, ts:Date.now()}); return 0; }
          const details = await detRes.json();
          // KEIN Badge für Movies, Series oder Schauspieler
          if (details && (details.Type === 'Movie' || details.Type === 'Series' || details.Type === 'Person')) {
            cache.set(id, {count:0, ts:Date.now()});
            return 0;
          }
          // Falls es ein Folder/Collection ist: nur Movie-Children zählen
          const listUrl = `${SERVER_URL}/Items?ParentId=${encodeURIComponent(id)}&Recursive=false&IncludeItemTypes=Movie`;
          const listRes = await fetch(listUrl, { headers: {'X-Emby-Token': apiKey} });
          if (!listRes.ok) { cache.set(id, {count:0, ts:Date.now()}); return 0; }
          const listData = await listRes.json();
          let movieCount = 0;
          if (listData && typeof listData.TotalRecordCount === 'number') movieCount = listData.TotalRecordCount;
          else if (listData && Array.isArray(listData.Items)) movieCount = listData.Items.length;
          movieCount = Number.isFinite(movieCount) ? Math.max(0, Math.floor(movieCount)) : 0;
          cache.set(id, {count: movieCount, ts: Date.now()});
          return movieCount;
        } catch (e) {
          console.error('fetchMovieCountForCollection error', e);
          cache.set(id, {count:0, ts:Date.now()});
          return 0;
        }
      }
      // --- Badge an Karte anhängen ---
      function attachBadge(card, count){
        if (!card || count <= 0) return;
        if (card.querySelector(`.${BADGE_CLASS}`)) return;
        if (getComputedStyle(card).position === 'static') card.style.position = 'relative';
        const b = document.createElement('div');
        b.className = BADGE_CLASS;
        b.textContent = String(count);
        card.appendChild(b);
      }
      // --- Karte annotieren ---
      async function annotateCard(card, apiKey){
        if (!card || card.dataset.__ct_annotated === '1') return;
        card.dataset.__ct_annotated = '1';
        const id = extractIdFromCard(card);
        if (!id) return;
        // Nur echte Movie-Collections bekommen ein Badge
        const movieCount = await fetchMovieCountForCollection(id, apiKey);
        if (movieCount > 0) attachBadge(card, movieCount);
      }
      // --- Initial annotieren ---
      async function annotateAll(apiKey){
        const cards = Array.from(document.querySelectorAll(CARD_SELECTOR));
        console.log('Film-Collection Annotator — Cards gefunden:', cards.length);
        cards.forEach((c, i) => setTimeout(() => annotateCard(c, apiKey).catch(err => console.error(err)), i * 60));
      }
      // --- MutationObserver für dynamisch nachgeladene Karten ---
      function observeMutations(apiKey){
        new MutationObserver(muts => {
          for (const m of muts) {
            for (const n of m.addedNodes) {
              if (n.nodeType !== 1) continue;
              if (n.matches && n.matches(CARD_SELECTOR)) annotateCard(n, apiKey);
              else if (n.querySelectorAll) {
                const found = n.querySelectorAll(CARD_SELECTOR);
                if (found && found.length) found.forEach(c => annotateCard(c, apiKey));
              }
            }
          }
        }).observe(document.body, { childList: true, subtree: true });
      }
      // --- Script starten ---
      const apiKey = await (async function waitForKey(timeout = 15000, interval = 200) {
        const start = Date.now();
        let key = null;
        while (!key && (Date.now() - start) < timeout) {
          try { key = (window.ApiClient?.accessToken?.() || window.JellyfinClient?.accessToken?.()); } catch(e){}
          if (!key) await new Promise(r => setTimeout(r, interval));
        }
        return key;
      })();
      if (!apiKey) {
        console.error('Kein Browser-Key gefunden – Badge kann nicht angezeigt werden.');
        alert('Kein Browser-Key gefunden!');
        return;
      }
      console.log('Browser-Key gefunden:', apiKey);
      annotateAll(apiKey);
      observeMutations(apiKey);
    })();
    « Next Oldest | Next Newest »

    Users browsing this thread: 1 Guest(s)


    • View a Printable Version
    • Subscribe to this thread
    Forum Jump:

    Home · Team · Help · Contact
    © Designed by D&D - Powered by MyBB
    L


    Jellyfin

    The Free Software Media System

    Linear Mode
    Threaded Mode