Skip to content

Feature Request: A standardised index file of videos and thumbnails for efficient WebDAV querying. #425

@zigzag-alchemist

Description

@zigzag-alchemist

Thanks for the great project! I was exploring streaming videos managed by YouTarr hosted via a WebDAV service, and discovered that it is very slow to index all of the subfolders. Each folder requires a new request, so with ~100 channels and 5 video history it can take a very long time to figure out what videos are available, and even longer to load all the .nfo and thumbnail files individually. I wrote a small script to make a single index.json file, which embeds the nfo and thumbnail data, so loading all the available video data in a folder is as quick as a single file download.

The exact format of the file is not particularly important, here I've used a simple json file, but having an index file in any standardised format would enable a client to access any WebDAV hostable filesystem that is synced to YouTarr to efficiently stay updated with the information YouTarr already knows about the video library, without performing exhaustive search over the whole file system each time it polls.

This is the python script I'm using combined with watchexec and it's working well so far for my custom client reader.

import os
import json
import base64
import xml.etree.ElementTree as ET
from pathlib import Path

def image_to_base64(image_path):
    """Converts an image to a base64 string with data URI prefix."""
    if not image_path or not os.path.exists(image_path):
        return None
    try:
        with open(image_path, "rb") as img_file:
            b64_string = base64.b64encode(img_file.read()).decode('utf-8')
            return f"data:image/jpeg;base64,{b64_string}"
    except Exception as e:
        print(f"Error encoding image {image_path}: {e}")
        return None

def nfo_to_dict(nfo_path):
    """Parses Kodi-style .nfo XML into a python dictionary."""
    if not os.path.exists(nfo_path):
        return {}
    try:
        tree = ET.parse(nfo_path)
        root = tree.getroot()
        
        def etree_to_dict(t):
            d = {}
            for child in t:
                if len(child) == 0:
                    value = child.text
                else:
                    value = etree_to_dict(child)           
                if child.tag in d:
                    if type(d[child.tag]) is list:
                        d[child.tag].append(value)
                    else:
                        d[child.tag] = [d[child.tag], value]
                else:
                    d[child.tag] = value
            return d
        return etree_to_dict(root)
    except Exception as e:
        print(f"Error parsing NFO {nfo_path}: {e}")
        return {}

def scan_folders(root_path):
    root = Path(root_path)
    data = {"channels": []}

    for channel_dir in [d for d in root.iterdir() if d.is_dir()]:
        print(f"Processing Channel: {channel_dir.name}...")    
        channel_obj = {
            "channel_name": channel_dir.name,
            "poster_data": image_to_base64(channel_dir / "poster.jpg"),
            "videos": []
        }
        for video_dir in [d for d in channel_dir.iterdir() if d.is_dir()]:
            video_file = next(video_dir.glob("*.mp4"), None)
            if video_file:
                nfo_file = next(video_dir.glob("*.nfo"), None)
                thumb_file = next(video_dir.glob("*.jpg"), None)
                video_data = {
                    "folder_name": video_dir.name,
                    "video_filename": video_file.name,
                    "thumbnail_data": image_to_base64(thumb_file) if thumb_file else None,
                    "metadata": nfo_to_dict(nfo_file) if nfo_file else {}
                }
                channel_obj["videos"].append(video_data)
        data["channels"].append(channel_obj)
    return data

if __name__ == "__main__":
    # This should point to the YouTarr directory (by default assumes the script is being run from that directory)
    ROOT_DIR = "." 
    OUTPUT_FILE = "index.json"

    result = scan_folders(ROOT_DIR)
    with open(OUTPUT_FILE, "w", encoding="utf-8") as f:
        json.dump(result, f, indent=2, ensure_ascii=False)

    print(f"\nSuccess! Index created at {OUTPUT_FILE}")

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions