diff --git a/.idea/CABC4L.iml b/.idea/CABC4L.iml index d870a4a..eab80d1 100644 --- a/.idea/CABC4L.iml +++ b/.idea/CABC4L.iml @@ -1,8 +1,10 @@ - - + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index ab530bf..850ef55 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/capture.py b/capture.py index 324b8c2..53d5edf 100644 --- a/capture.py +++ b/capture.py @@ -12,6 +12,7 @@ gi.require_version('Gst', '1.0') from gi.repository import GObject, Gst from multiprocessing.connection import Listener +from time import sleep DBusGMainLoop(set_as_default=True) Gst.init(None) @@ -73,10 +74,11 @@ def play_pipewire_stream(node_id): maxcheckpoint = 100 immersive_multiplier = 1.8 offset = 0 - mode = 'normal' + mode = 'disabled' running = True listener = Listener(address=("localhost", 21827)) + blistener = Listener(address=("localhost", 21828)) empty_dict = dbus.Dictionary(signature="sv") fd_object = portal.OpenPipeWireRemote(session, empty_dict, @@ -94,11 +96,11 @@ def play_pipewire_stream(node_id): # Read and display frames from the pipeline conn = listener.accept() + connb = blistener.accept() backlight = int(subprocess.check_output("light -r", shell=True)) while running: while conn.poll(): msg = conn.recv() - print(f"got message: {msg}") if msg == "EXIT": running = False @@ -106,53 +108,66 @@ def play_pipewire_stream(node_id): margin = int(msg[1]) elif msg[0] == "target": target = int(msg[1]) - ret, frame = cap.read() - - if not ret: - print('=--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=') - break - - - # Calculate the overall image of the grayscale frame cv2.resize(frame, (100, 100)) - image = float(cv2.cvtColor(cv2.resize(frame, (200, 200)), cv2.COLOR_BGR2GRAY).mean()) # by resizing the image , the program only cost 0.3-0.5w on my r7 - if checkcnt >= checkpoint: - checkcnt = 0 - buff = int(subprocess.check_output("light -r", shell=True)) - if buff == backlight: - if checkpoint != maxcheckpoint: - checkpoint = maxcheckpoint - else: - pass - else : - checkpoint = 1 - print('something is wrong, backlight is : '+str(buff) + ' instead of '+str(backlight)+', trying to set again') - backlight = buff - #print("target : "+str(target)+" Content: "+str(image)+ " backlight: "+str(backlight)) - if mode == 'normal': - if int(image) <= 1: - if backlight == 0: - pass - else: - subprocess.call(('light', '-S', '-r', str(0))) - backlight = 0 - - elif abs((backlight*image)//255 - target) > margin: - val = int((target/image)*255) - if val > 255: - subprocess.call(('light', '-S', '-r', '255')) + elif msg[0] == "mode": + mode = str(msg[1]) + elif msg[0] == "offset": + offset = int(msg[1]) + elif msg[0] == "multiplier": + immersive_multiplier = float(msg[1]) + if mode != "disabled": + while connb.poll(): + msg = connb.recv() + if msg == "value": + connb.send(backlight) + ret, frame = cap.read() + + if not ret: + print('=--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=') + break + + + # Calculate the overall image of the grayscale frame cv2.resize(frame, (100, 100)) + image = float(cv2.cvtColor(cv2.resize(frame, (200, 200)), cv2.COLOR_BGR2GRAY).mean()) # by resizing the image , the program only cost 0.3-0.5w on my r7 + if checkcnt >= checkpoint: + checkcnt = 0 + buff = int(subprocess.check_output("light -r", shell=True)) + if buff == backlight: + if checkpoint != maxcheckpoint: + checkpoint = maxcheckpoint + else: + pass + else : + checkpoint = 1 + print('something is wrong, backlight is : '+str(buff) + ' instead of '+str(backlight)+', trying to set again') + backlight = buff + #print("target : "+str(target)+" Content: "+str(image)+ " backlight: "+str(backlight)) + if mode == 'normal': + if int(image) <= 1: + if backlight == 0: + pass + else: + subprocess.call(('light', '-S', '-r', str(0))) + backlight = 0 + + elif abs((backlight*image)//255 - target) > margin: + val = int((target/image)*255) + if val > 255: + subprocess.call(('light', '-S', '-r', '255')) + backlight = 255 + else: + subprocess.call(('light','-S', '-r', str(val))) + backlight = val + elif mode == 'immersive': + if int(image*immersive_multiplier)+offset > 255: + subprocess.call(('light', '-S', '-r', str(255))) backlight = 255 else: - subprocess.call(('light','-S', '-r', str(val))) - backlight = val - elif mode == 'immersive': - if int(image*immersive_multiplier)+offset > 255: - subprocess.call(('light', '-S', '-r', str(255))) - backlight = 255 - else: - backlight = int(image*immersive_multiplier+offset) - subprocess.call(('light','-S', '-r', str(backlight))) - print(backlight) - checkcnt += 1 + backlight = int(image*immersive_multiplier+offset) + subprocess.call(('light','-S', '-r', str(backlight))) + #print(backlight) + checkcnt += 1 + else: + sleep(0.5) # Release the VideoCapture object and close windows cap.release() cv2.destroyAllWindows() diff --git a/main.py b/main.py index be4f944..a090a56 100644 --- a/main.py +++ b/main.py @@ -4,23 +4,36 @@ gi.require_version('Adw', '1') from gi.repository import Gtk, Adw from multiprocessing.connection import Client +from threading import Thread +from time import sleep class MainWindow(Gtk.ApplicationWindow): def __init__(self, *args, **kwargs): + self.brunning = True + self.running = True super().__init__(*args, **kwargs) self.Mainbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) - self.SliderBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + self.NormalBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + self.ImmersiveBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + + self.lightbar = Gtk.LevelBar(orientation=Gtk.Orientation.HORIZONTAL) + self.lightbar.set_min_value(0) + self.lightbar.set_max_value(255) + self.set_child(self.Mainbox) + self.initModeSelect() + self.initMultiplier() + self.initOffset() self.initTarget() self.initMargin() - self.Mainbox.append(self.SliderBox) + #self.Mainbox.append(self.NormalBox) self.header = Gtk.HeaderBar() self.set_titlebar(self.header) self.button = Gtk.Button(label="Hello") - self.header.pack_start(self.button) + #self.header.pack_start(self.button) self.button.connect('clicked', self.hello) while True: try: @@ -28,7 +41,153 @@ def __init__(self, *args, **kwargs): break except: pass + while True: + try: + self.bconn = Client(address=("localhost", 21828)) + break + except: + pass + self.connect("close-request", self.closea) + + Bart = Thread(target=self.bars) + Bart.start() + + def closea(self,arg): + self.brunning = False + self.running = False + self.close() + + def initModeSelect(self): + self.buttonBox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) + self.radio1 = Gtk.CheckButton(label="disable") + self.radio2 = Gtk.CheckButton(label="normal") + self.radio3 = Gtk.CheckButton(label="cinema(lcd)") + self.radio2.set_group(self.radio1) + self.radio3.set_group(self.radio1) + self.radio1.connect("toggled", self.toggled) + self.radio2.connect("toggled", self.toggled) + self.radio3.connect("toggled", self.toggled) + self.buttonBox.append(self.radio1) + self.buttonBox.append(self.radio2) + self.buttonBox.append(self.radio3) + self.radio1.set_active(True) + self.Mainbox.append(self.buttonBox) + + def toggled(self,arg): + if self.radio1.get_active(): + self.brunning = False + try: + self.conn.send(["mode","disabled"]) + except: + pass + + try: + self.Mainbox.remove(self.NormalBox) + except: + pass + + try: + self.Mainbox.remove(self.ImmersiveBox) + except: + pass + + try: + self.Mainbox.remove(self.lightbar) + except: + pass + + elif self.radio2.get_active(): + self.conn.send(["mode","normal"]) + self.brunning = True + try: + self.Mainbox.remove(self.ImmersiveBox) + except: + pass + + try: + self.Mainbox.remove(self.lightbar) + self.Mainbox.append(self.lightbar) + except: + self.Mainbox.append(self.lightbar) + + self.Mainbox.append(self.NormalBox) + + elif self.radio3.get_active(): + self.conn.send(["mode","immersive"]) + self.brunning = True + try: + self.Mainbox.remove(self.NormalBox) + except: + pass + + try: + self.Mainbox.remove(self.lightbar) + self.Mainbox.append(self.lightbar) + except: + self.Mainbox.append(self.lightbar) + + self.Mainbox.append(self.ImmersiveBox) + def bars(self): + while self.running: + while self.brunning : + self.bconn.send("value") + try : + self.lightbar.set_value(self.bconn.recv()) + except: + try: + self.Mainbox.remove(self.lightbar) + self.Mainbox.append(self.lightbar) + except: + self.Mainbox.append(self.lightbar) + sleep(0.03) + sleep(0.5) + def initOffset(self): + self.offsetbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + self.offsetadj = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) + self.offsetframe = Gtk.Frame() + self.offsetframe.set_margin_top(15) + self.offsetframe.set_margin_end(10) + self.offsetframe.set_child(self.offsetbox) + self.offsetlabel = Gtk.Label() + self.offsetlabel.set_margin_top(10) + self.offsetlabel.set_label("offset") + + self.offsetslider = Gtk.Scale() + self.offsetslider.set_digits(2) # Number of decimal places to use + self.offsetslider.set_range(0, 254) + self.offsetslider.set_draw_value(True) # Show a label with current value + self.offsetslider.set_value(1) # Sets the current value/position + self.offsetslider.connect('value-changed', self.offsetslider_changed) + self.offsetslider.set_hexpand(True) # + + self.offsetadj.append(self.offsetslider) + self.offsetbox.append(self.offsetlabel) + self.offsetbox.append(self.offsetadj) + self.ImmersiveBox.append(self.offsetframe) + def initMultiplier(self): + self.multiplierbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + self.multiplieradj = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) + self.multiplierframe = Gtk.Frame() + self.multiplierframe.set_margin_top(15) + self.multiplierframe.set_margin_end(10) + self.multiplierframe.set_child(self.multiplierbox) + self.multiplierlabel = Gtk.Label() + self.multiplierlabel.set_margin_top(10) + self.multiplierlabel.set_label("multiplier") + + self.multiplierslider = Gtk.Scale() + self.multiplierslider.set_digits(2) # Number of decimal places to use + self.multiplierslider.set_range(0.1, 4) + self.multiplierslider.set_draw_value(True) # Show a label with current value + self.multiplierslider.set_value(1) # Sets the current value/position + self.multiplierslider.connect('value-changed', self.multiplierslider_changed) + self.multiplierslider.set_hexpand(True) # + + self.multiplieradj.append(self.multiplierslider) + self.multiplierbox.append(self.multiplierlabel) + self.multiplierbox.append(self.multiplieradj) + self.ImmersiveBox.append(self.multiplierframe) def initTarget(self): self.targetbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) @@ -52,7 +211,7 @@ def initTarget(self): self.targetadj.append(self.targetslider) self.targetbox.append(self.targetlabel) self.targetbox.append(self.targetadj) - self.SliderBox.append(self.targetframe) + self.NormalBox.append(self.targetframe) def initMargin(self): self.marginbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) @@ -76,11 +235,17 @@ def initMargin(self): self.marginadj.append(self.marginslider) self.marginbox.append(self.marginlabel) self.marginbox.append(self.marginadj) - self.SliderBox.append(self.marginframe) + self.NormalBox.append(self.marginframe) def targetslider_changed(self, slider): self.conn.send(["target",int(self.targetslider.get_value())]) + def multiplierslider_changed(self, slider): + self.conn.send(["multiplier",float(self.multiplierslider.get_value())]) + + def offsetslider_changed(self, slider): + self.conn.send(["offset",int(self.offsetslider.get_value())]) + def marginslider_changed(self, slider): self.conn.send(["margin",int(self.marginslider.get_value())]) diff --git a/readme.md b/readme.md index 6d63632..31889fc 100644 --- a/readme.md +++ b/readme.md @@ -1,7 +1,20 @@ -Content Adaptive Brightness Control for linux +# Content-Based Adaptive Brightness Control for Linux +## needed -need `light` package to work and python opencv + pyGobject libs +need `light` package to work and python `opencv` + `pyGobject` libs -state : WORKING ON A CONTRAST BOOST MODE FOR LCD SCREENS, IT ALLOWS TO MAKE IPS'S PANNELS ALMOST AS GOOD AS AMOLED FOR WATCHING VIDEOS/FILMS +## how to use +run `start.py` in a terminal, hit `ctrl+c` to close +### modes +#### normal +As you saw on Windows 11, to maintain a 'constant light level' to not blow your eyes up +#### cinema +Only usefull for lcd-based screens +adapts backlight in realtime with content brightness to make dark scenes dark and bright scenes bright +recommend using it in a dark environnement +best to watch films/videos -run `start.py` \ No newline at end of file +## incoming improvements +- a smart mode for cinema to adjust multiplier and offset automatically +- an installer +- some stability and performance improvements \ No newline at end of file diff --git a/start.py b/start.py index c6aabaf..93cb27a 100644 --- a/start.py +++ b/start.py @@ -14,5 +14,6 @@ def gui(): Pdaemon.start() sleep(0.5) Pgui.start() -Pdaemon.join() Pgui.join() +Pdaemon.close() +