Skip to content

Commit 2c7e625

Browse files
committed
Volume bar added
1 parent 9681da8 commit 2c7e625

File tree

3 files changed

+69
-5
lines changed

3 files changed

+69
-5
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ ffmpeg.exe
66
windows
77
macos
88
mac
9-
linux
9+
linux
10+
options.json

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ Ever wanted to watch videos as colored ASCII art in your terminal, with sound, a
3232
- **Q**: Quit
3333
- **A/D**: Rewind/Forward 5 seconds
3434
- **←/→ (Arrow keys)**: Skip 1 second
35+
- **M**: Mute/Unmute
36+
- **+/-**: Volume Up/Down
3537

3638
---
3739

vidminal.py

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import io # string jail
99
import shutil # delete stuff fast
1010
import atexit # clean up my mess
11+
import subprocess # command line wizard
1112

1213
# stderr: go to sleep
1314
class SuppressStderr:
@@ -349,6 +350,10 @@ def play_ascii_video_stream_streaming(folder, audio, frame_queue, total_frames,
349350
stop_flag = threading.Event()
350351
pause_flag = threading.Event()
351352
pause_flag.clear()
353+
playback_state = {
354+
'volume': 1.0,
355+
'is_muted': False,
356+
}
352357
rewind_forward = pyqueue.Queue()
353358

354359
def keyboard_listener():
@@ -366,6 +371,15 @@ def keyboard_listener():
366371
pause_flag.set()
367372
elif key in ('q', 'Q'):
368373
stop_flag.set()
374+
elif key in ('m', 'M'):
375+
playback_state['is_muted'] = not playback_state['is_muted']
376+
elif key in ('-', '_'):
377+
# Decrease volume, ensure it doesn't go below 0
378+
playback_state['volume'] = max(0.0, round(playback_state['volume'] - 0.1, 1))
379+
elif key in ('+', '='):
380+
# Increase volume, ensure it doesn't go above 1.0, and unmute
381+
playback_state['volume'] = min(1.0, round(playback_state['volume'] + 0.1, 1))
382+
playback_state['is_muted'] = False
369383
elif key in ('a', 'A'):
370384
rewind_forward.put(-5 * speed) # rewind 5s
371385
elif key in ('d', 'D'):
@@ -395,6 +409,7 @@ def keyboard_listener():
395409

396410
def play_audio_from(pos):
397411
pygame.mixer.music.load(audio)
412+
pygame.mixer.music.set_volume(0.0 if playback_state['is_muted'] else playback_state['volume'])
398413
pygame.mixer.music.play(start=pos)
399414

400415
def format_time(t):
@@ -469,6 +484,11 @@ def format_time(t):
469484
sleep_for = tgt - now
470485
if sleep_for > 0:
471486
time.sleep(sleep_for)
487+
488+
# Apply volume changes from keyboard continuously
489+
current_vol = 0.0 if playback_state['is_muted'] else playback_state['volume']
490+
if pygame.mixer.music.get_volume() != current_vol:
491+
pygame.mixer.music.set_volume(current_vol)
472492
# Check terminal size
473493
try:
474494
term_size = shutil.get_terminal_size()
@@ -487,13 +507,23 @@ def format_time(t):
487507
print('Buffering...'.center(actual_frame_wide), end='\n') # Use actual_frame_wide for centering
488508
# --- Seek bar with play/pause and time ---
489509
play_emoji = '⏸️' if not pause_flag.is_set() else '▶️'
510+
511+
# Volume UI
512+
vol_bar_width = 10
513+
vol_icon = '🔇' if playback_state['is_muted'] or playback_state['volume'] == 0 else '🔊'
514+
if playback_state['is_muted']:
515+
vol_bar = '-' * vol_bar_width
516+
else:
517+
vol_level = int(playback_state['volume'] * vol_bar_width)
518+
vol_bar = '█' * vol_level + '-' * (vol_bar_width - vol_level)
519+
vol_str = f" {vol_icon}[{vol_bar}]"
520+
490521
time_str = f"{format_time(i / speed)} / {format_time(total_time)}"
491-
# Assume emoji is 2 cells wide. Total fixed width is emoji(2) + " [] "(4) + time_str
492-
fixed_len = 2 + 4 + len(time_str)
522+
fixed_len = 2 + 4 + len(time_str) + len(vol_str)
493523
bar_width = max(1, actual_frame_wide - fixed_len) # Use actual_frame_wide here
494524
bar_pos = int((i / (total_frames - 1)) * bar_width) if total_frames > 1 else 0
495525
bar = '█' * bar_pos + '-' * (bar_width - bar_pos)
496-
print(f"{play_emoji} [{bar}] {time_str}")
526+
print(f"{play_emoji} [{bar}] {time_str}{vol_str}")
497527
# --- End seek bar ---
498528
i += 1
499529
if frames_buffer:
@@ -533,7 +563,9 @@ def box_line(text, color=text_color):
533563
f"{box_color}{'═'* (box_width-2)}{reset}",
534564
box_line(" Turns videos into ugly terminal art. With sound. "),
535565
box_line(" Made by a lazy coder. " + Fore.MAGENTA + "@github/SajagIN" + text_color + " "),
536-
box_line(f" {Fore.GREEN}Space{reset}{text_color} = pause {Fore.GREEN}Q{reset}{text_color} = quit {Fore.GREEN}A/D{reset}{text_color} = rewind/forward 5s "),
566+
box_line(f" {Fore.GREEN}Space{reset}{text_color} = pause/play {Fore.GREEN}Q{reset}{text_color} = quit"),
567+
box_line(f" {Fore.GREEN}A/D{reset}{text_color} = seek 5s {Fore.GREEN}←/→{reset}{text_color} = seek 1s"),
568+
box_line(f" {Fore.GREEN}M{reset}{text_color} = mute {Fore.GREEN}+/-{reset}{text_color} = volume"),
537569
f"{box_color}{'═'* (box_width-2)}{reset}",
538570
""
539571
]
@@ -566,6 +598,35 @@ def box_line(text, color=text_color):
566598
atexit.unregister_all = getattr(atexit, 'unregister_all', lambda: None) # For repeated runs in interactive mode
567599
atexit.unregister_all()
568600
atexit.register(lambda: cleanup_temp_folder(temp))
601+
602+
# --- Downscale to 144p if needed ---
603+
# Ensure temp exists
604+
if not os.path.exists(temp):
605+
os.makedirs(temp)
606+
# Check video resolution
607+
try:
608+
clip = VideoFileClip(vid)
609+
w, h = clip.size
610+
clip.close()
611+
if h > 144:
612+
# Downscale to 144p and use as new source
613+
downscaled_path = os.path.join(temp, 'downscaled_144p.mp4')
614+
# Use ffmpeg from env or system
615+
ffmpeg_bin = os.environ.get('FFMPEG_BINARY', 'ffmpeg')
616+
# -y to overwrite, -vf scale=-2:144 to keep aspect
617+
cmd = [ffmpeg_bin, '-y', '-i', vid, '-vf', 'scale=-2:144', '-c:v', 'libx264', '-preset', 'ultrafast', '-crf', '28', '-c:a', 'copy', downscaled_path]
618+
try:
619+
subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
620+
vid = downscaled_path
621+
except Exception as e:
622+
print(Fore.RED + f"Downscaling failed: {e}" + reset)
623+
sys.exit(1)
624+
# else: use original vid
625+
except Exception as e:
626+
print(Fore.RED + f"Failed to check video resolution: {e}" + reset)
627+
sys.exit(1)
628+
# --- End downscale logic ---
629+
569630
try:
570631
frames, audio, frame_queue, total_frames, video_duration = get_stuff_from_video_stream(vid, temp, speed=fps, buffer_size=fps)
571632
print(Fore.GREEN + Style.BRIGHT + 'Streaming ASCII video...' + reset)

0 commit comments

Comments
 (0)