Compare commits

...

2 Commits

Author SHA1 Message Date
nose
9384169c0e Merge branch 'main' of https://code.glowers.club/goyimnose/Medios-Macina 2025-11-25 22:36:47 -08:00
nose
4df4fb3bd9 kk 2025-11-25 22:34:41 -08:00
7 changed files with 170 additions and 97 deletions

2
.gitignore vendored
View File

@@ -6,7 +6,6 @@ __pycache__/
config.json
# C extensions
*.so
# Distribution / packaging
.Python
build/
@@ -217,3 +216,4 @@ luac.out
*.hex
config.json

63
CLI.py
View File

@@ -30,12 +30,16 @@ try:
from prompt_toolkit import PromptSession
from prompt_toolkit.completion import Completer, Completion
from prompt_toolkit.document import Document
from prompt_toolkit.lexers import Lexer
from prompt_toolkit.styles import Style
PROMPT_TOOLKIT_AVAILABLE = True
except ImportError: # pragma: no cover - optional dependency
PromptSession = None # type: ignore
Completer = None # type: ignore
Completion = None # type: ignore
Document = None # type: ignore
Lexer = None # type: ignore
Style = None # type: ignore
PROMPT_TOOLKIT_AVAILABLE = False
@@ -531,6 +535,46 @@ if (
async def get_completions_async(self, document: Document, complete_event): # type: ignore[override]
for completion in self.get_completions(document, complete_event):
yield completion
class MedeiaLexer(Lexer):
def lex_document(self, document):
def get_line(lineno):
line = document.lines[lineno]
tokens = []
import re
# Match: Whitespace, Pipe, Quoted string, or Word
pattern = re.compile(r'''
(\s+) | # 1. Whitespace
(\|) | # 2. Pipe
("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*') | # 3. Quoted string
([^\s\|]+) # 4. Word
''', re.VERBOSE)
is_cmdlet = True
for match in pattern.finditer(line):
ws, pipe, quote, word = match.groups()
if ws:
tokens.append(('', ws))
elif pipe:
tokens.append(('class:pipe', pipe))
is_cmdlet = True
elif quote:
tokens.append(('class:string', quote))
is_cmdlet = False
elif word:
if is_cmdlet:
tokens.append(('class:cmdlet', word))
is_cmdlet = False
elif word.startswith('-'):
tokens.append(('class:argument', word))
else:
tokens.append(('class:value', word))
return tokens
return get_line
else: # pragma: no cover - prompt toolkit unavailable
CmdletCompleter = None # type: ignore[assignment]
@@ -586,7 +630,21 @@ Example: search-file --help
if PROMPT_TOOLKIT_AVAILABLE and PromptSession is not None and CmdletCompleter is not None:
completer = CmdletCompleter()
session = PromptSession(completer=cast(Any, completer))
# Define style for syntax highlighting
style = Style.from_dict({
'cmdlet': '#ffffff', # white
'argument': '#3b8eea', # blue-ish
'value': '#ce9178', # red-ish
'string': '#ce55ff', # purple
'pipe': '#4caf50', # green
})
session = PromptSession(
completer=cast(Any, completer),
lexer=MedeiaLexer(),
style=style
)
def get_input(prompt: str = ">>>|") -> str:
return session.prompt(prompt)
@@ -645,6 +703,7 @@ Example: search-file --help
if last_table is None:
last_table = ctx.get_last_result_table()
if last_table:
print()
# Also update current stage table so @N expansion works correctly
@@ -779,10 +838,10 @@ def _execute_pipeline(tokens: list):
else:
# Try command-based expansion first if we have source command info
command_expanded = False
selected_row_args = []
if source_cmd:
# Try to find row args for the selected indices
selected_row_args = []
for idx in first_stage_selection_indices:
row_args = ctx.get_current_stage_table_row_selection_args(idx)
if row_args:

View File

@@ -1,64 +0,0 @@
# Medeia-Macina
A powerful CLI media management and search platform integrating local files, Hydrus, torrents, books, and P2P networks.
## Key Features
* **Unified Search**: Search across Local, Hydrus, LibGen, Soulseek, and Debrid.
* **Pipeline Architecture**: Chain commands like PowerShell (e.g., `search | filter | download`).
* **Smart Selection**: Use `@N` syntax to interact with results.
* **Metadata Management**: Tagging, notes, and relationships.
## Installation
1. Install Python 3.9+ and [Deno](https://deno.com/) (for YouTube support).
2. Install dependencies: `pip install -r requirements.txt`
3. Run the CLI: `python CLI.py`
## Command Examples
### Search & Download
```powershell
# Search and download the first result
search-file "daughter" | @1 | download-data
# Search specific provider and download
search-file -provider libgen "dune" | @1 | download-data
# Download YouTube video (auto-probes formats)
download-data "https://youtube.com/watch?v=..."
# Select format #2 from the list
@2 | download-data
```
### File Management
```powershell
# Add file to Hydrus
add-file -path "C:\Videos\movie.mp4" -storage hydrus
# Upload to 0x0.st and associate URL with Hydrus file
search-file "my_video" | @1 | add-file -provider 0x0
# Add tags to a file
search-file "video" | @1 | add-tag "creator:someone, character:hero"
# Use tag lists (from helper/adjective.json)
@1 | add-tag "{gnostic}"
```
### Metadata & Notes
```powershell
# Add a note
search-file "doc" | @1 | add-note "comment" "This is important"
# Get tags
search-file "image" | @1 | get-tag
```
### Pipeline Syntax
* `|` : Pipe results from one command to another.
* `@N` : Select the Nth item from the previous result (e.g., `@1`).
* `@N-M` : Select a range (e.g., `@1-5`).
* `@{1,3,5}` : Select specific items.
* `@*` : Select all items.
## Configuration
Edit `config.json` to set API keys (AllDebrid, OpenAI), storage paths, and Hydrus credentials.

View File

@@ -1,8 +1,6 @@
# Medios-Macina
media management
first: edit the config.json and save, if you dont have certain things use null with no quotes,
second: install the python application
python cli.py
@@ -11,9 +9,3 @@ python cli.py
2. @1
1. download-data "https://altrusiangrace.bandcamp.com/album/ancient-egyptian-legends-full-audiobook" | merge-file | add-file -storage local
1. screen-shot "https://code.glowers.club/goyimnose/Medios-Macina" | add-tag "mmmm" | add-file -storage local
2. search-file "mmmm"
3. @1 | get-file

View File

@@ -2459,6 +2459,38 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any], emit_results:
if downloaded_files or files_downloaded_directly > 0:
total_files = len(downloaded_files) + files_downloaded_directly
log(f"✓ Successfully downloaded {total_files} file(s)", flush=True)
# Create a result table for the downloaded files
# This ensures that subsequent @N commands select from these files
# instead of trying to expand the previous command (e.g. search-file)
if downloaded_files:
from result_table import ResultTable
table = ResultTable("Downloaded Files")
for i, file_path in enumerate(downloaded_files):
row = table.add_row()
row.add_column("#", str(i + 1))
row.add_column("File", file_path.name)
row.add_column("Path", str(file_path))
try:
size_mb = file_path.stat().st_size / (1024*1024)
row.add_column("Size", f"{size_mb:.1f} MB")
except OSError:
row.add_column("Size", "?")
# Set selection args to just the file path (or index if we want item selection)
# For item selection fallback, we don't strictly need row args if source command is None
# But setting them helps if we want to support command expansion later
table.set_row_selection_args(i, [str(file_path)])
# Register the table but DO NOT set a source command
# This forces CLI to use item-based selection (filtering the pipe)
# instead of command expansion
pipeline_context.set_last_result_table_overlay(table, downloaded_files)
pipeline_context.set_current_stage_table(table)
# Also print the table so user sees what they got
log(str(table), flush=True)
if db:
db.update_worker_status(worker_id, 'completed')
return 0

View File

@@ -104,6 +104,29 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
clear_mode = parsed.get("clear")
list_mode = parsed.get("list")
play_mode = parsed.get("play")
pause_mode = parsed.get("pause")
# Handle Play/Pause commands
if play_mode:
cmd = {"command": ["set_property", "pause", False], "request_id": 103}
resp = _send_ipc_command(cmd)
if resp and resp.get("error") == "success":
log("Resumed playback")
return 0
else:
log("Failed to resume playback (MPV not running?)", file=sys.stderr)
return 1
if pause_mode:
cmd = {"command": ["set_property", "pause", True], "request_id": 104}
resp = _send_ipc_command(cmd)
if resp and resp.get("error") == "success":
log("Paused playback")
return 0
else:
log("Failed to pause playback (MPV not running?)", file=sys.stderr)
return 1
# Handle piped input (add to playlist)
if result:
@@ -132,15 +155,19 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
if target:
# Add to MPV playlist
# We use loadfile with append flag
# Configure 1080p limit for streams (bestvideo<=1080p + bestaudio)
options = {
"ytdl-format": "bestvideo[height<=?1080]+bestaudio/best[height<=?1080]"
}
# Use memory:// M3U hack to pass title to MPV
# This avoids "invalid parameter" errors with loadfile options
# and ensures the title is displayed in the playlist/window
if title:
options["force-media-title"] = title
# Sanitize title for M3U (remove newlines)
safe_title = title.replace('\n', ' ').replace('\r', '')
m3u_content = f"#EXTM3U\n#EXTINF:-1,{safe_title}\n{target}"
target_to_send = f"memory://{m3u_content}"
else:
target_to_send = target
cmd = {"command": ["loadfile", target, "append", options], "request_id": 200}
cmd = {"command": ["loadfile", target_to_send, "append"], "request_id": 200}
resp = _send_ipc_command(cmd)
if resp is None:
@@ -154,6 +181,18 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
log(f"Queued: {title}")
else:
log(f"Queued: {target}")
else:
error_msg = str(resp.get('error'))
log(f"Failed to queue item: {error_msg}", file=sys.stderr)
# If error indicates parameter issues, try without options
# (Though memory:// should avoid this, we keep fallback just in case)
if "option" in error_msg or "parameter" in error_msg:
cmd = {"command": ["loadfile", target, "append"], "request_id": 201}
resp = _send_ipc_command(cmd)
if resp and resp.get("error") == "success":
added_count += 1
log(f"Queued (fallback): {title or target}")
if added_count > 0:
# If we added items, we might want to play the first one if nothing is playing?
@@ -198,6 +237,10 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
cmd = {"command": ["playlist-play-index", idx], "request_id": 102}
resp = _send_ipc_command(cmd)
if resp and resp.get("error") == "success":
# Ensure playback starts (unpause)
unpause_cmd = {"command": ["set_property", "pause", False], "request_id": 103}
_send_ipc_command(unpause_cmd)
log(f"Playing: {title}")
return 0
else:
@@ -275,8 +318,6 @@ def _start_mpv(items: List[Any]) -> None:
cmd.append('--ytdl-format=bestvideo[height<=?1080]+bestaudio/best[height<=?1080]')
# Add items
first_title_set = False
for item in items:
target = None
title = None
@@ -291,9 +332,12 @@ def _start_mpv(items: List[Any]) -> None:
target = item
if target:
if not first_title_set and title:
cmd.append(f'--force-media-title={title}')
first_title_set = True
if title:
# Use memory:// M3U hack to pass title
safe_title = title.replace('\n', ' ').replace('\r', '')
m3u_content = f"#EXTM3U\n#EXTINF:-1,{safe_title}\n{target}"
cmd.append(f"memory://{m3u_content}")
else:
cmd.append(target)
if len(cmd) > 3: # mpv + ipc + format + at least one file
@@ -329,6 +373,16 @@ CMDLET = Cmdlet(
type="flag",
description="List items (default)"
),
CmdletArg(
name="play",
type="flag",
description="Resume playback"
),
CmdletArg(
name="pause",
type="flag",
description="Pause playback"
),
],
exec=_run
)

View File

@@ -2,22 +2,22 @@
"debug": false,
"provider": {
"openlibrary": {
"email": "e@.com",
"password": "pass"
"email": "dewibi7691@lagsixtome.com",
"password": "3t4J3NKSrV8sPsoY2"
},
"soulseek": {
"password": "whateveryouwant",
"username": "pickrandomcantbeinuse"
"password": "rndpass",
"username": "doioae3432"
}
},
"storage": {
"debrid": {
"All-debrid": "put in your api key for debrid here"
"All-debrid": "YutC4nxq3zimg6ttMUji"
},
"hydrus": {
"home": {
"key": "hydrus-access-key-goes-here",
"url": "http://192.168.1.###:45869"
"key": "d4321f178b10cb40b3ef604f864aa90bd6c27b3b44361f8e701315dcc22f1e1e",
"url": "http://192.168.1.230:45869"
},
"work": {
"key": null,
@@ -25,8 +25,8 @@
}
},
"local": {
"path": "C:\\your\\local\\database\\folder"
"path": "C:\\Media Machina"
}
},
"temp": "C:\\do\\not\\use\\system\\temp"
"temp": "C:\\Users\\Admin\\Downloads"
}