initial commit

This commit is contained in:
Pierre
2026-01-11 17:26:49 +01:00
commit 888790fd11
240 changed files with 48691 additions and 0 deletions
@@ -0,0 +1,6 @@
# FIX CRLF always when developer has not set
# Linux/Mac: git config --global core.autocrlf input
# Windows: git config --global core.autocrlf true
# Auto detect text files and perform LF normalization
* text=auto
* eol=lf
+29
View File
@@ -0,0 +1,29 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
**/node_modules/**
/.pnp
.pnp.js
.pnp.cjs
.pnp.loader.mjs
.yarn
# testing
/coverage
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
.vscode
npm-debug.log*
yarn-debug.log*
yarn-error.log*
/junit.xml
/.nyc_output/
/coverage/
/dist/
.idea
@@ -0,0 +1,4 @@
{
"loader": "ts-node/esm",
"spec": "src/**/*.test.ts"
}
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Francisco Hodge
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+176
View File
@@ -0,0 +1,176 @@
# Meta Interface
Shared TypeScript interfaces and types for the Meta Orbit ecosystem, providing type definitions used across all packages.
## Overview
Meta Interface is the foundational package that defines common TypeScript interfaces, types, and constants shared across the entire Meta Orbit monorepo. It has no dependencies and serves as the base layer for type safety.
## Features
- **Zero Dependencies**: Foundation package with no external dependencies
- **Type Definitions**: Comprehensive TypeScript interfaces for metadata structures
- **Shared Constants**: Common enums and constant values
- **Platform Agnostic**: Works in both Node.js and browser environments
- **Strict Typing**: Enables type safety across all packages
## Installation
This package is automatically available in the monorepo workspace:
```json
{
"dependencies": {
"@metazla/meta-interface": "workspace:*"
}
}
```
## Usage
```typescript
import type {
HashMeta,
FileMetadata,
VideoType,
MediaType
} from '@metazla/meta-interface';
// Use interfaces for type safety
const metadata: HashMeta = {
cid_sha256: 'baejbei...',
fileName: 'episode.mkv',
sizeByte: '1024000',
videoType: 'tvshow',
season: '1',
episode: '1',
titles: { eng: 'Show Name' }
};
```
## Key Interfaces
### HashMeta
Complete metadata structure for media files:
```typescript
interface HashMeta {
// Content identifiers
cid_sha1?: string;
cid_sha256?: string;
cid_md5?: string;
cid_sha3_256?: string;
cid_sha3_384?: string;
cid_crc32?: string;
// File information
fileName: string;
extension?: string;
sizeByte: string;
fileType?: MediaType;
// Video metadata
videoType?: VideoType;
season?: string;
episode?: string;
originalTitle?: string;
titles?: Record<string, string>;
// Classification
anime?: string;
genres?: string[];
languages?: string[];
tags?: string[];
// Paths
privateFilePath?: string;
publicFilePath?: string;
}
```
### VideoType
```typescript
type VideoType = 'movie' | 'tvshow' | 'anime' | 'documentary' | 'unknown';
```
### MediaType
```typescript
type MediaType = 'video' | 'audio' | 'image' | 'document' | 'unknown';
```
## Architecture
### Build Configuration
Built with `tsup` for dual output:
- **ESM**: `dist/index.js`
- **CommonJS**: `dist/index.cjs`
### Package Structure
```
meta-interface/
├── src/
│ └── lib/
│ ├── interfaces.ts # Core interface definitions
│ ├── types.ts # Type aliases and unions
│ ├── constants.ts # Shared constants
│ └── index.ts # Public exports
├── package.json
└── tsconfig.json
```
## Development
### Building
```bash
# From monorepo root
pnpm run build
# From package directory
cd packages/meta-interface
pnpm run build
```
### Testing
```bash
cd packages/meta-interface
pnpm test
```
Uses Mocha for type validation and interface tests.
## Used By
All packages in the monorepo depend on meta-interface:
- **meta-hash**: Hash metadata structures
- **meta-db**: Database record types
- **meta-mesh**: File processing interfaces
- **meta-orbit**: Network message types
- **meta-ui**: UI component prop types
- **filename-tool**: Parsing result types
## Dependency Flow
```
meta-interface (foundation, no deps)
↓
All other packages depend on it
```
This ensures:
- Consistent types across the entire codebase
- Single source of truth for interfaces
- Type safety in all inter-package communication
## Contributing
When adding new interfaces:
1. Add to appropriate file in `src/lib/`
2. Export from `src/lib/index.ts`
3. Document with JSDoc comments
4. Ensure backward compatibility
This package is part of the Meta Orbit monorepo. See the root README for contribution guidelines.
## License
MIT - Same as Meta Orbit monorepo
@@ -0,0 +1,40 @@
{
"name": "@metazla/meta-interface",
"version": "1.0.0",
"description": "List of interfaces for the Meta project",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"typesVersions": {
"*": {
"dist/index.d.ts": [
"dist/index.d.ts"
],
"*": [
"dist/*"
]
}
},
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
},
"scripts": {
"build": "cross-env tsc && tsup",
"test": "mocha"
},
"devDependencies": {
"@types/chai": "^4.3.11",
"@types/mocha": "^10.0.6",
"@types/node": "^20.12.2",
"chai": "^5.0.3",
"cross-env": "^7.0.3",
"mocha": "^10.2.0",
"ts-node": "^10.9.2",
"tsup": "^8.0.2",
"typescript": "^5.4.3"
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,50 @@
import {describe, beforeEach, it} from 'mocha';
import {expect} from 'chai';
import {from, MetadataNode} from './MetaDataAPI.js';
import {VideoMeta} from "./metadata-type/VideoMeta";
import {GenreMeta} from "./metadata-type/GenreMeta";
type TypedMetaData = VideoMeta & GenreMeta;
describe('MetaData', () => {
let root: MetadataNode<TypedMetaData>;
let meta = {};
beforeEach(() => {
// Reset meta for each test to ensure no test pollution
meta = {};
root = from<TypedMetaData>(meta);
});
it('should set and get a simple key-value pair', () => {
root.at('originalTitle').set("Star Wars");
expect(root.at('originalTitle').get()).to.equal("Star Wars");
});
it('should set and get nested metadata using nested keys', () => {
root.at('titles').at('eng').set("Star Wars");
root.at('titles').at('jpn').set("Staru Warsu");
expect(root.at('titles').at('eng').get()).to.equal("Star Wars");
expect(root.at('titles').at('jpn').get()).to.equal("Staru Warsu");
expect(root.at('titles').get()).to.deep.equal({eng: "Star Wars", jpn: "Staru Warsu"});
expect(root.get()).to.deep.equal({
titles: {eng: "Star Wars", jpn: "Staru Warsu"}
});
});
it('should return undefined for non-existent keys', () => {
expect(root.at<any>('nonexistent').at<any>('nonexistent2').get()).to.equal(undefined);
});
it('should handle overriding existing keys with new values', () => {
root.at('genres').add("Sci-fi");
expect(root.at('genres').getKeysAsSet()).to.deep.equal(new Set(["Sci-fi"]));
// Override the value
root.at('genres').add("Adventure");
expect(root.at('genres').getKeysAsSet()).to.deep.equal(new Set(['Sci-fi', 'Adventure']));
root.at('genres').add("Sci-fi");
expect(root.at('genres').getKeysAsSet()).to.deep.equal(new Set(['Sci-fi', 'Adventure']));
//root.at('genres').remove('Sci-fi');
//expect(root.at('genres').getKeys()).to.deep.equal(new Set(['Adventure']));
});
});
@@ -0,0 +1,115 @@
export function from<T>(metadata: T): MetadataNode<T> {
return new MetadataNode<T>([], metadata);
}
export class MetadataNode<T> {
constructor(private path: string[], private rootData: any, private replacementRule?: (oldValue: T, newValue: T) => T) {
this.replacementRule = replacementRule || ((oldValue: T, newValue: T) => {
console.warn(`Key ${path.join('.')} already exists and no replacement rule defined (default to taking the new one).
Current value: ${oldValue}
New value: ${newValue}`);
return newValue;
});
}
// Navigate to a subnode without ensuring it exists for further operations
at<K extends keyof T>(key: K): MetadataNode<T[K]> {
if (!key) {
throw new Error('Key is required');
}
const newPath = [...this.path, key as string];
return new MetadataNode<T[K]>(newPath, this.rootData);
}
// Set the value at the current node, ensuring the path exists
set(value: T): void {
if (value === undefined || value === null) {
//we don't want to set undefined or null values
return;
}
const current = this.ensurePath(true);
if (this.path.length > 0) {
const lastKey = this.path[this.path.length - 1];
if (current[lastKey]) {
if (current[lastKey] === value) {
return;
}
current[lastKey] = this.replacementRule(current[lastKey], value);
} else {
current[lastKey] = value;
}
}
}
// Get the value at the current node
get(defaultValue?: T): T {
const current = this.ensurePath(false);
return current === undefined ? defaultValue : current;
}
// this is used to define set of values
add(key: string): void {
if (key === undefined || key === null) {
//we don't want to set undefined or null values
return;
}
let node = this.get();
if (node && node instanceof Set) {
const set = node as Set<string>;
if (set.has(key)) {
return;
} else {
set.add(key);
}
} else {
this.set(new Set([key]) as any);
}
}
// Remove a key from the node
/*remove<K extends keyof T>(key: K|string): void {
const current = this.ensurePath(false);
if (current && current[key] !== undefined) {
delete current[key];
}
}*/
// Get all keys at the current node
getKeysAsSet(): Set<string> {
const current = this.ensurePath(false);
if(current instanceof Set){
return current as Set<string>;
}else {
return new Set(current ? Object.keys(current) as Array<string> : []);
}
}
getKeysAsArray(): string[] {
const current = this.ensurePath(false);
if(!current){
return [];
}
if(current instanceof Set){
return Array.from(current as Set<string>);
}else {
return Object.keys(current);
}
}
private ensurePath(createPath: boolean): any {
let current = this.rootData;
const path = createPath ? this.path.slice(0, -1) : this.path;
for (const segment of path) {
if (current[segment] === undefined) {
if (createPath) {
current[segment] = {};
} else {
return undefined; // Return undefined early if the path segment doesn't exist and we're not creating it
}
}
current = current[segment];
}
return current;
}
}
@@ -0,0 +1,74 @@
export const FileMetaDataName = [
"fileType",
"mimeType",
"byteSize",
]
export const hashMetaDataName = [
"cid_crc32",
"cid_md5",
"cid_sha1",
"cid_sha2-256",
"cid_sha3-256",
"cid_sha3_384",
];
export const AnimeMetaData = [
"anime"
];
export const FFMpegMetaData = [
"fileinfo"
];
export const FileNameMetaData = [
"fileName",
"extension",
"videoType",
"extra",
"season",
"episode",
"increment",
"movieYear",
"title",
"titles",
];
export const TorrentMetaData = [
"trakers"
];
export const VideoMetaData = [
"subtitles"
];
export const JellyfinMetaData = [
"videoType", // Noted as optional and with specific types in your comment, but listed as a simple string here
"episode",
"season",
"title",
"year",
"originaltitle",
"imdbid",
"tmdbid",
"anidbid",
"premiered",
"genre",
"releasedate",
"plot",
"director",
"rating",
"runtime",
"studio",
"art",
"actor",
"showtitle",
"aired",
"customrating",
"dateadded",
"sorttitle",
"mpaa",
"aspectratio",
"collectionnumber",
"criticrating",
];
@@ -0,0 +1 @@
create a lib for that
@@ -0,0 +1,26 @@
export * from './MetaDataAPI';
export * from './MetaDataName';
export * from './metadata-type/basic/FileRef';
export * from './metadata-type/basic/LanguageData';
export * from './metadata-type/basic/RecordSet';
export * from './metadata-type/basic/SimpleFileType';
export * from './metadata-type/basic/StringBoolean';
export * from './metadata-type/basic/VideoType';
export * from "./metadata-type/basic/ArrayKey";
export * from './metadata-type/AnimeMeta';
export * from './metadata-type/DebugMeta';
export * from './metadata-type/FileNameMeta';
export * from './metadata-type/FileStatMeta';
export * from './metadata-type/GenreMeta';
export * from './metadata-type/HashMeta';
export * from './metadata-type/JellyfinMeta';
export * from './metadata-type/LanguagesMeta';
export * from './metadata-type/MediaStreamMeta';
export * from './metadata-type/SubtitleMeta';
export * from './metadata-type/TagsMeta';
export * from './metadata-type/TorrentMeta';
export * from './metadata-type/VideoMeta';
export * from './metadata-type/VersionMeta';
@@ -0,0 +1,9 @@
import {StringBoolean} from "./basic/StringBoolean.js";
export interface AnimeMeta {
anime?: StringBoolean;
}
export const AnimeMetaFields = [
"anime",
];
@@ -0,0 +1,7 @@
export interface DebugMeta {
privateFilePath?: string;
}
export const DebugMetaFields = [
"privateFilePath",
];
@@ -0,0 +1,9 @@
export interface FileNameMeta {
fileName?: string;
extension?: string;
}
export const FileNameMetaFields = [
"fileName",
"extension",
];
@@ -0,0 +1,12 @@
export interface FileStatMeta {
fileType?:string;
mimeType?:string;
sizeByte?:string;
}
export const FileStatMetaFields = [
"fileType",
"mimeType",
"sizeByte",
];
@@ -0,0 +1,9 @@
import {RecordSet} from "./basic/RecordSet.js";
export interface GenreMeta{
genres?: RecordSet;
}
export const GenreMetaFields = [
"genres",
];
@@ -0,0 +1,32 @@
/**
* HashMeta - Content identification hashes
*
* Primary Global ID: cid_midhash256 (SHA-256 hash of middle 1MB + file size)
* - Format: CID v1 with custom multicodec 0x1000
* - Example: bafkr4ih5kapbjzqvmj7jxr... (base32 CID encoding)
*
* Legacy IDs: cid_sha2-256, cid_sha1 (computed optionally for compatibility)
*
* Migration Note: cid_midhash256 is the new primary identifier replacing full SHA-256.
* Old hashes remain for backward compatibility and external system integration.
* All CIDs now use proper CID v1 format (IPFS-compatible structure).
*/
export interface HashMeta {
cid_crc32?: string; //0x0132 CRC32 (SFV)
cid_md5?: string; // 0xd5 md5
cid_sha1?: string; // 0x11 sha1
"cid_sha2-256"?: string;//0x12 sha2-256
"cid_sha3-256"?: string; // 0x16 sha3-256
"cid_sha3_384"?: string; // 0x16 sha3-256
cid_midhash256?: string; // 0x1000 midhash256 (CID v1 format with custom multicodec)
}
export const HashMetaFields = [
"cid_crc32",
"cid_md5",
"cid_sha1",
"cid_sha2-256",
"cid_sha3-256",
"cid_sha3_384",
"cid_midhash256",
];
@@ -0,0 +1,64 @@
import {LanguageData} from "./basic/LanguageData.js";
import {RecordSet} from "./basic/RecordSet.js";
import {VideoMeta} from "./VideoMeta.js";
import {TagsMeta} from "./TagsMeta.js";
import {LanguagesMeta} from "./LanguagesMeta.js";
import {GenreMeta} from "./GenreMeta.js";
import {FileRef} from "./basic/FileRef";
export interface IMDBMeta{
imdbid?: string;
criticrating?: string;
}
export interface TMDBMeta{
tmdbid?: string;
}
export interface AnidbMeta{
anidbid?: string;
}
export interface JellyfinMeta extends
IMDBMeta,
TMDBMeta,
AnidbMeta,
VideoMeta,
TagsMeta,
LanguagesMeta,
GenreMeta
{
releasedate?: string;
plot?: LanguageData<string>;//key is language ISO 639-1 code
rating?: string;
studio?: RecordSet;
art?:{
poster?: FileRef;
fanart?: FileRef;
};
mpaa?: string;
//TODO add more fields in the future
}
export const JellyfinMetaFields = [
"imdbid",
"tmdbid",
"anidbid",
"extra",
"season",
"episode",
"increment",
"movieYear",
"videoType",
"originalTitle",
"titles",
"tags",
"languages",
"genre",
"releasedate",
"plot",
"rating",
"studio",
"art",
"mpaa",
];
@@ -0,0 +1,10 @@
import {TypedRecordSet} from "./basic/RecordSet.js";
import {ISO639_1_CODE} from "./basic/LanguageData.js";
export interface LanguagesMeta{
languages?: TypedRecordSet<ISO639_1_CODE>;
}
export const LanguagesMetaFields = [
"languages",
];
@@ -0,0 +1,59 @@
import {StringBoolean} from "./basic/StringBoolean.js";
import {ArrayKey} from "./basic/ArrayKey";
export interface VideoStream {
width?: string;
height?: string;
bitrate?: string;
frameRate?: string;
rotation?: string;
language?: string;
}
export interface SubtitleStream {
language?: string;
title?: string;
}
export interface AudioStream {
sampleRate?: string;
bitrate?: string;
language?: string;
channelLayout?: string;
}
export interface CommonStream {
index?: string;
duration?: string;
codecType?: string;
codec?: string;
language?: string;
title?: string;
default?: StringBoolean;
forced?: StringBoolean;
}
export type Stream = VideoStream & SubtitleStream & AudioStream & CommonStream;
/**
* StreamList is a list of streams
* key is stream index in string "0","1","2" etc
*/
export type StreamList = Record<ArrayKey,Stream>;
export interface MediaStreamMeta {
fileinfo?: {
duration?: string;
formatName?: string;
streamdetails?: {
video: StreamList;
audio: StreamList;
subtitle: StreamList;
embeddedimage: StreamList;
}
}
}
export const MediaStreamMetaFields = [
"fileinfo",
];
@@ -0,0 +1,23 @@
import {LanguageData} from "./basic/LanguageData.js";
import {TypedRecordSet} from "./basic/RecordSet.js";
import {FileRef} from "./basic/FileRef.js";
//used by video files to store the subtitle files
export interface SubtitleMeta {
subtitles?:LanguageData<TypedRecordSet<FileRef>>;//key is the language code, value is a set of ipfs hash of the subtitle file
}
//used by subtitle files to store the video files
export interface SubtitleFileMeta {
videos?:TypedRecordSet<FileRef>;//a set of ipfs hash of the video file
subtitleLanguage?:string;//the language of the subtitle file
}
export const SubtitleMetaFields = [
"subtitles",
];
export const SubtitleFileMetaFields = [
"videos",
"subtitleLanguage",
];
@@ -0,0 +1,9 @@
import {RecordSet} from "./basic/RecordSet.js";
export interface TagsMeta {
tags?: RecordSet;
}
export const TagsMetaFields = [
"tags",
];
@@ -0,0 +1,25 @@
import {RecordSet} from "./basic/RecordSet.js";
export interface TorrentMeta {
announce?: string;
announceList?: RecordSet;
comment?: string;
createdBy?: string;
creationDate?: number;
info: {
files: Record<string, {
length: number;
path: string;
}>;// key is the "0" "1" "2" etc
name: string;
};
}
export const TorrentMetaFields = [
"announce",
"announceList",
"comment",
"createdBy",
"creationDate",
"info",
];
@@ -0,0 +1,7 @@
export interface VersionMeta {
version: string; //usually a V1 V2 V3 etc..
}
export const VersionMetaFields = [
"version"
];
@@ -0,0 +1,24 @@
import {LanguageData} from "./basic/LanguageData.js";
import {VideoType} from "./basic/VideoType";
export interface VideoMeta {
extra?: string;
season?: string;
episode?: string;
increment?: string;
movieYear?: string;
videoType?: VideoType;
originalTitle?: string;//title in original language
titles?: LanguageData<string>;// <ISO 639-3 lang codes, title>
}
export const VideoMetaFields = [
"extra",
"season",
"episode",
"increment",
"movieYear",
"videoType",
"originalTitle",
"titles",
];
@@ -0,0 +1,6 @@
// used to simulate array using map and object
export type ArrayKey = "id_0" | "id_1" | "id_2" | "id_3" | "id_4" | "id_5" | "id_6" | "id_7" | "id_8" | "id_9";
export function createArrayKey (index: number): ArrayKey {
return `id_${index}` as ArrayKey;
}
@@ -0,0 +1,4 @@
export type FileRef = URLString | FilePathString | CIDString;
export type URLString = string;
export type FilePathString = string;
export type CIDString = string;
@@ -0,0 +1,12 @@
/**
* key is language ISO 639-3 code
* https://en.wikipedia.org/wiki/List_of_ISO_639-3_codes
*/
export type LanguageData<T> = Record<string, T>;
export type ISO639_1_CODE = string;
/**
* jpn, jpr and jpa already exist. Creating jpl is for japan-latin. Used for japanese romaji NON ISO 639-3 standard.
* */
export const romajiIsoCode = "jpl";
export const englishIsoCode = "eng";
@@ -0,0 +1,2 @@
export type RecordSet = Set<string>;
export type TypedRecordSet<T extends string> = Set<T>;
@@ -0,0 +1,2 @@
export type SimpleFileType = 'audio' | 'video' | 'document' | 'archive' | 'other' | 'undefined';
@@ -0,0 +1 @@
export type StringBoolean = "true" | "false";
@@ -0,0 +1 @@
export type VideoType = "movie" | "tvshow";
@@ -0,0 +1,14 @@
<!-- Example XSD: mediaFileSchema.xsd -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.example.com/schema" xmlns="http://www.example.com/schema" elementFormDefault="qualified">
<xs:element name="root">
<xs:complexType>
<xs:sequence>
<xs:element name="fileType" type="xs:string"/>
<xs:element name="mimeType" type="xs:string"/>
<xs:element name="byteSize" type="xs:long"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
@@ -0,0 +1,18 @@
{
"compilerOptions": {
"noImplicitAny": false,
"module": "esnext",
"target": "esnext",
"allowJs": true,
"sourceMap": true,
"esModuleInterop":true,
"skipLibCheck": true,
"noEmit": true,
"moduleResolution": "node",
"baseUrl": "./",
},
"include": [
"src/lib"
],
"exclude": ["node_modules/**"]
}
@@ -0,0 +1,15 @@
import {defineConfig} from 'tsup'
export default defineConfig({
entry: {
'index': 'src/lib/index.ts',
},
outDir: 'dist',
splitting: true,
treeshake: true,
sourcemap: true,
clean: true,
dts:true,
minify:false,
format: ['cjs', 'esm'],
})