// Package media serves generic item endpoints: detail, the raw file, cover // art, and progress. It knows nothing about any specific media format; modules // register cover extractors and expose their own metadata endpoints. package media import ( "context" "database/sql" "errors" "fmt" "path/filepath" ) // ErrItemNotFound means no item matched. var ErrItemNotFound = errors.New("media: item not found") // Item is one media item plus the root of its library. type Item struct { ID int64 LibraryID int64 Kind string Path string // path relative to the library root Title string Year int Missing bool // RootPath is the absolute library root, so AbsPath can be computed. RootPath string } // AbsPath returns the absolute filesystem path of the item's file. // // item.path is stored relative to the library root and the root is stored // resolved, so this join cannot escape the library. func (i Item) AbsPath() string { return filepath.Join(i.RootPath, filepath.FromSlash(i.Path)) } // LoadItem loads an item and its library root. func LoadItem(ctx context.Context, db *sql.DB, id int64) (Item, error) { var it Item err := db.QueryRowContext(ctx, ` SELECT i.id, i.library_id, i.kind, i.path, i.title, COALESCE(i.year, 0), i.missing, l.root_path FROM media_items i JOIN libraries l ON l.id = i.library_id WHERE i.id = ?`, id, ).Scan(&it.ID, &it.LibraryID, &it.Kind, &it.Path, &it.Title, &it.Year, &it.Missing, &it.RootPath) if errors.Is(err, sql.ErrNoRows) { return Item{}, ErrItemNotFound } if err != nil { return Item{}, fmt.Errorf("media: load item: %w", err) } return it, nil } // FileEntry is one physical file backing an item. type FileEntry struct { ID int64 `json:"id"` Path string `json:"path"` Size int64 `json:"size"` Container string `json:"container"` Missing bool `json:"missing"` } // FileCount returns how many files back an item. func FileCount(ctx context.Context, db *sql.DB, itemID int64) (int, error) { var n int err := db.QueryRowContext(ctx, `SELECT count(*) FROM media_files WHERE item_id = ?`, itemID).Scan(&n) return n, err }