Skip to content

Commit 877a117

Browse files
committed
Fix tqdm in-place refresh and route files to Output Dir
Terminal: - Use pty.openpty() so subprocess sees a real terminal; tqdm/rich now use \r for in-place refresh instead of \n-per-update line spam - Parse \r to reset the current-line buffer (new content overwrites); \n finalises the line and starts a new ListView item - Strip ANSI escape codes before display - Cap ListView at 500 lines (trim oldest) to prevent rendering stalls Output directory: - Pass --path OUTPUT_DIR to all downloader.py calls from the GUI so videos and materials land in the user-chosen Output Dir, not ~/.auto_note/ - semantic_alignment.py: read OUTPUT_DIR from config.json into COURSE_DATA_DIR; use it for process_course() instead of DATA_DIR - note_generation.py: same COURSE_DATA_DIR pattern for --course lookup
1 parent 460ce7c commit 877a117

3 files changed

Lines changed: 105 additions & 31 deletions

File tree

gui.py

Lines changed: 85 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -457,38 +457,91 @@ def _append_line(text: str, color: str | None = None) -> None:
457457
def _worker() -> None:
458458
rc = -1
459459
try:
460-
env = {**os.environ, "PYTHONUNBUFFERED": "1"}
460+
import pty as _pty, io as _io
461+
env = {**os.environ, "PYTHONUNBUFFERED": "1",
462+
"TERM": "xterm-256color", "COLUMNS": "100"}
463+
master_fd, slave_fd = _pty.openpty()
461464
state.proc = subprocess.Popen(
462465
cmd,
463-
stdout=subprocess.PIPE,
464-
stderr=subprocess.STDOUT,
465-
text=True,
466+
stdout=slave_fd, stderr=slave_fd,
467+
close_fds=True,
466468
cwd=str(_get_output_dir()),
467-
bufsize=1,
468469
env=env,
469470
)
471+
os.close(slave_fd)
472+
473+
_MAX_LINES = 500
474+
_ANSI_RE = re.compile(
475+
r"\x1b\[[0-9;]*[mABCDEFGHJKST]|\x1b\][^\x07]*\x07"
476+
)
470477
_last_update = time.monotonic()
471-
_pending = False
472-
for line in state.proc.stdout:
473-
stripped = line.strip()
474-
if stripped.startswith("<frozen importlib"):
475-
continue
476-
color = None
477-
low = stripped.lower()
478-
if low.startswith(("error", "traceback", "exception")):
479-
color = C_ERROR
480-
elif low.startswith("warning"):
481-
color = C_WARN
482-
_append_line(line.rstrip(), color)
483-
_pending = True
484-
now = time.monotonic()
485-
if now - _last_update >= _UPDATE_INTERVAL:
486-
self.page.update()
487-
_last_update = now
488-
_pending = False
489-
490-
if _pending:
491-
self.page.update()
478+
buf = "" # current line being assembled
479+
480+
def _cur() -> ft.Text:
481+
"""Return (or create) the last Text item — the live line."""
482+
if not self._lines.controls:
483+
t = ft.Text("", size=11, font_family=MONO,
484+
color=_default_color, no_wrap=False,
485+
selectable=True)
486+
self._lines.controls.append(t)
487+
return self._lines.controls[-1]
488+
489+
def _new_line() -> None:
490+
"""Append a blank Text item; trim oldest if over cap."""
491+
self._lines.controls.append(
492+
ft.Text("", size=11, font_family=MONO,
493+
color=_default_color, no_wrap=False,
494+
selectable=True)
495+
)
496+
excess = len(self._lines.controls) - _MAX_LINES
497+
if excess > 0:
498+
del self._lines.controls[:excess]
499+
500+
try:
501+
with _io.open(master_fd, "rb", closefd=True) as master:
502+
while True:
503+
try:
504+
chunk = master.read(4096)
505+
except OSError:
506+
break
507+
if not chunk:
508+
break
509+
text = _ANSI_RE.sub(
510+
"", chunk.decode("utf-8", errors="replace")
511+
)
512+
for ch in text:
513+
if ch == "\r":
514+
# Carriage return: next chars overwrite
515+
# the current line — discard old buffer.
516+
buf = ""
517+
elif ch == "\n":
518+
if not buf.startswith("<frozen importlib"):
519+
item = _cur()
520+
item.value = buf
521+
low = buf.lower().lstrip()
522+
if low.startswith(
523+
("error", "traceback",
524+
"exception")):
525+
item.color = C_ERROR
526+
elif low.startswith("warning"):
527+
item.color = C_WARN
528+
_new_line()
529+
buf = ""
530+
else:
531+
buf += ch
532+
533+
now = time.monotonic()
534+
if now - _last_update >= _UPDATE_INTERVAL:
535+
if buf:
536+
_cur().value = buf
537+
self.page.update()
538+
_last_update = now
539+
except OSError:
540+
pass
541+
542+
if buf:
543+
_cur().value = buf
544+
self.page.update()
492545

493546
state.proc.wait()
494547
rc = state.proc.returncode
@@ -791,14 +844,16 @@ def _run(_):
791844
cmds: list[tuple[str, list[str]]] = []
792845
if "dl_material" in steps:
793846
c = [PYTHON, str(SCRIPTS["downloader"]),
794-
"--course", str(cid), "--download-material-all"]
847+
"--course", str(cid), "--download-material-all",
848+
"--path", str(_get_output_dir())]
795849
if secretly_sw.value:
796850
c.append("--secretly")
797851
cmds.append(("Download materials", c))
798852

799853
if "dl_video" in steps:
800854
c = [PYTHON, str(SCRIPTS["downloader"]),
801-
"--course", str(cid), "--download-video-all"]
855+
"--course", str(cid), "--download-video-all",
856+
"--path", str(_get_output_dir())]
802857
if secretly_sw.value:
803858
c.append("--secretly")
804859
cmds.append(("Download videos", c))
@@ -886,7 +941,8 @@ def _secretly_args() -> list[str]:
886941
return ["--secretly"] if secretly_sw.value else []
887942

888943
def _go(extra: list[str]) -> None:
889-
console.run([PYTHON, str(SCRIPTS["downloader"])]
944+
console.run([PYTHON, str(SCRIPTS["downloader"]),
945+
"--path", str(_get_output_dir())]
890946
+ _course_args() + extra + _secretly_args())
891947

892948
scroll_content = [

note_generation.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,15 @@
3737
else:
3838
DATA_DIR = PROJECT_DIR
3939

40+
# Course output directory: defaults to DATA_DIR but can be overridden by
41+
# OUTPUT_DIR in config.json so files land in the user's chosen Output Dir.
42+
_ng_config: dict = (
43+
json.loads((DATA_DIR / "config.json").read_text())
44+
if (DATA_DIR / "config.json").exists() else {}
45+
)
46+
_out_dir = _ng_config.get("OUTPUT_DIR", "").strip()
47+
COURSE_DATA_DIR = Path(_out_dir) if _out_dir else DATA_DIR
48+
4049
# ── Constants ─────────────────────────────────────────────────────────────────
4150

4251
DETAIL_LEVEL = 7
@@ -1305,7 +1314,7 @@ def main() -> None:
13051314
args = parser.parse_args()
13061315

13071316
if args.course:
1308-
course_dir = DATA_DIR / args.course
1317+
course_dir = COURSE_DATA_DIR / args.course
13091318
course_name = args.course_name or f"CS{args.course}"
13101319
lectures = _discover_lectures(course_dir)
13111320
if args.lectures:

semantic_alignment.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,15 @@
4646
else:
4747
DATA_DIR = PROJECT_DIR
4848

49+
# Course output directory: defaults to DATA_DIR but can be overridden by
50+
# OUTPUT_DIR in config.json so files land in the user's chosen Output Dir.
51+
_sa_config: dict = (
52+
json.loads((DATA_DIR / "config.json").read_text())
53+
if (DATA_DIR / "config.json").exists() else {}
54+
)
55+
_out_dir = _sa_config.get("OUTPUT_DIR", "").strip()
56+
COURSE_DATA_DIR = Path(_out_dir) if _out_dir else DATA_DIR
57+
4958
# ── Tunable knobs ─────────────────────────────────────────────────────────────
5059

5160
EMBED_MODEL = "all-mpnet-base-v2" # highest-quality general sentence model
@@ -1088,7 +1097,7 @@ def _content_match_slide_group(
10881097

10891098

10901099
def process_course(course_id: int | str) -> None:
1091-
course_dir = DATA_DIR / str(course_id)
1100+
course_dir = COURSE_DATA_DIR / str(course_id)
10921101
captions = sorted((course_dir / "captions").glob("*.json"))
10931102
all_slides = _candidate_slides(course_dir)
10941103
out_dir = course_dir / "alignment"

0 commit comments

Comments
 (0)