support color output in console

This commit is contained in:
ArabCoders 2024-12-11 20:50:49 +03:00
parent 3b7338eb79
commit 82f59676c7
3 changed files with 81 additions and 41 deletions

View file

@ -4,8 +4,10 @@ import argparse
import asyncio import asyncio
import base64 import base64
from datetime import datetime from datetime import datetime
import errno
import json import json
import os import os
import pty
import random import random
import shlex import shlex
import time import time
@ -603,46 +605,80 @@ class Main:
await self.sio.emit('cli_close', {'exitcode': 0}) await self.sio.emit('cli_close', {'exitcode': 0})
return return
proc = None
try: try:
async def _read_stream(streamType, stream):
while True:
line = await stream.readline()
if line:
await self.sio.emit('cli_output', {
'type': streamType,
'line': line.decode('utf-8')
})
else:
break
LOG.info(f'CLI: {data}') LOG.info(f'CLI: {data}')
args = ['yt-dlp', *shlex.split(data)] args = ['yt-dlp'] + shlex.split(data)
_env = os.environ.copy()
_env.update({
"TERM": "xterm-256color",
"LANG": "en_US.UTF-8",
"SHELL": "/bin/bash",
"LC_ALL": "en_US.UTF-8",
"PWD": self.config.download_path,
"FORCE_COLOR": "1",
"PYTHONUNBUFFERED": "1",
})
master_fd, slave_fd = pty.openpty()
proc = await asyncio.create_subprocess_exec( proc = await asyncio.create_subprocess_exec(
*args, *args,
cwd=self.config.download_path, cwd=self.config.download_path,
stdin=asyncio.subprocess.DEVNULL, stdin=asyncio.subprocess.DEVNULL,
stdout=asyncio.subprocess.PIPE, stdout=slave_fd,
stderr=asyncio.subprocess.PIPE, stderr=slave_fd,
env=os.environ.copy().update({ env=_env,
"TERM": "xterm-256color",
"LANG": "en_US.UTF-8",
"SHELL": "/bin/bash",
"LC_ALL": "en_US.UTF-8",
"PWD": self.config.download_path,
}),
) )
await asyncio.gather( try:
_read_stream('stdout', proc.stdout), os.close(slave_fd)
_read_stream('stderr', proc.stderr), except Exception as e:
) LOG.error(f'Error closing PTY: {str(e)}')
while proc.returncode is None: async def read_pty():
await asyncio.sleep(0.1) loop = asyncio.get_running_loop()
buffer = b''
while True:
try:
chunk = await loop.run_in_executor(None, lambda: os.read(master_fd, 1024))
except OSError as e:
if e.errno == errno.EIO:
break
else:
raise
await self.sio.emit('cli_close', {'exitcode': proc.returncode}) if not chunk:
# No more output
if buffer:
# Emit any remaining partial line
await self.sio.emit('cli_output', {
'type': 'stdout',
'line': buffer.decode('utf-8', errors='replace')
})
break
buffer += chunk
*lines, buffer = buffer.split(b'\n')
for line in lines:
await self.sio.emit('cli_output', {
'type': 'stdout',
'line': line.decode('utf-8', errors='replace')
})
try:
os.close(master_fd)
except Exception as e:
LOG.error(f'Error closing PTY: {str(e)}')
# Start reading output from PTY
read_task = asyncio.create_task(read_pty())
# Wait until process finishes
returncode = await proc.wait()
# Ensure reading is done
await read_task
await self.sio.emit('cli_close', {'exitcode': returncode})
except Exception as e: except Exception as e:
LOG.error(f'CLI Error: {str(e)}') LOG.error(f'CLI Error: {str(e)}')
await self.sio.emit('cli_output', { await self.sio.emit('cli_output', {

View file

@ -15,7 +15,8 @@
<span class="icon"><font-awesome-icon icon="fa-solid fa-terminal" /></span> Terminal <span class="icon"><font-awesome-icon icon="fa-solid fa-terminal" /></span> Terminal
</p> </p>
<p class="card-header-icon"> <p class="card-header-icon">
<span v-tooltip.top="'Clear console window'" class="icon" @click="clearOutput"><font-awesome-icon icon="fa-solid fa-broom" /></span> <span v-tooltip.top="'Clear console window'" class="icon" @click="clearOutput"><font-awesome-icon
icon="fa-solid fa-broom" /></span>
</p> </p>
</header> </header>
<section class="card-content p-0 m-0"> <section class="card-content p-0 m-0">
@ -23,16 +24,23 @@
</section> </section>
<section class="card-content p-1 m-1"> <section class="card-content p-1 m-1">
<div class="field is-grouped"> <div class="field is-grouped">
<div class="control is-expanded has-icons-left"> <div class="control">
<span class="icon-text input is-unselectable">
<span class="icon"><font-awesome-icon icon="fa-solid fa-terminal" /></span>
<span>yt-dlp</span>
</span>
</div>
<div class="control is-expanded">
<input type="text" class="input" v-model="command" placeholder="ls -la" autocomplete="off" <input type="text" class="input" v-model="command" placeholder="ls -la" autocomplete="off"
ref="command_input" @keydown.enter="runCommand" :disabled="props.isLoading" id="command"> ref="command_input" @keydown.enter="runCommand" :disabled="props.isLoading" id="command">
<span class="icon is-left"><font-awesome-icon icon="fa-solid fa-terminal"
:spin="props.isLoading" /></span>
</div> </div>
<p class="control"> <p class="control">
<button class="button is-primary" type="button" :disabled="props.isLoading || '' === command" <button class="button is-primary" type="button" :disabled="props.isLoading || '' === command"
@click="runCommand"> @click="runCommand">
<span class="icon"><font-awesome-icon icon="fa-solid fa-paper-plane" /></span> <span class="icon">
<font-awesome-icon icon="fa-solid fa-spinner" spin v-if="props.isLoading" />
<font-awesome-icon icon="fa-solid fa-paper-plane" v-else />
</span>
</button> </button>
</p> </p>
</div> </div>
@ -132,7 +140,8 @@ const runCommand = async () => {
} }
emitter('runCommand', command.value) emitter('runCommand', command.value)
terminal.value.writeln(`~ ${command.value}`) terminal.value.writeln(`user@YTPTube ~`)
terminal.value.writeln(`$ yt-dlp ${command.value}`)
} }
const clearOutput = async (withCommand = false) => { const clearOutput = async (withCommand = false) => {

View file

@ -7,12 +7,7 @@ import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { import {
faCog, faTrash, faLink, faPlus, faTrashCan, faCircleXmark, faCircleCheck, faRotateRight, faDownload, faUpRightFromSquare, faCog, faTrash, faLink, faPlus, faTrashCan, faCircleXmark, faCircleCheck, faRotateRight, faDownload, faUpRightFromSquare,
faSpinner, faArrowUp, faArrowDown, faTasks, faCalendar, faArrowUpAZ, faArrowDownAZ, faEject, faGlobe, faMoon, faSun, faSpinner, faArrowUp, faArrowDown, faTasks, faCalendar, faArrowUpAZ, faArrowDownAZ, faEject, faGlobe, faMoon, faSun,
faTerminal, faTerminal, faBroom, faServer, faPowerOff, faPaperPlane, faBoxArchive,
faBroom,
faServer,
faPowerOff,
faPaperPlane,
faBoxArchive
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import { faSquare, faSquareCheck } from '@fortawesome/free-regular-svg-icons' import { faSquare, faSquareCheck } from '@fortawesome/free-regular-svg-icons'