diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 13afd40..51b09f9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -31,9 +31,9 @@ jobs: uses: docker/metadata-action@v4 with: images: ghcr.io/dmunozv04/isponsorblocktv, dmunozv04/isponsorblocktv -# tags: | -# type=raw,value=latest,priority=900,enable=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }} -# type=ref,enable=true,priority=600,prefix=pr-,suffix=,event=pr + tags: | + type=raw,value=develop,priority=900,enable=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }} + type=ref,enable=true,priority=600,prefix=pr-,suffix=,event=pr # https://github.com/docker/setup-qemu-action - name: Set up QEMU diff --git a/.gitignore b/.gitignore index a9ada3d..47669b1 100644 --- a/.gitignore +++ b/.gitignore @@ -155,7 +155,7 @@ cython_debug/ #config folder data/ -config.json +data/config.json .DS_Store diff --git a/README.md b/README.md index dc82c9f..92b9f0e 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,49 @@ # iSponsorBlockTV +Skip sponsor segments in YouTube videos playing on a YouTube TV device (see below for compatibility details). -Skip sponsor segments in YouTube videos playing on an Apple TV. Sponsor Block in YouTube for apple TV +This project is written in asynchronous python and should be pretty quick. -This project is written in asycronous python and should be pretty quick. - -# Installation +## Installation Check the [wiki](https://github.com/dmunozv04/iSponsorBlockTV/wiki/Installation) -Warning: armv7 builds have been deprecated. +Warning: docker armv7 builds have been deprecated. Amd64 and arm64 builds are still available. -# Usage +## Compatibility +Leyend: ✅ = Working, ❌ = Not working, ❔ = Not tested -Run iSponsorBLockTV on the same network as the Apple TV. +Open an issue/pull request if you have tested a device that isn't listed here. -It connects to the Apple TV, watches its activity and skips any sponsor segment using the [SponsorBlock](https://sponsor.ajay.app/) API. +| Device | Status | +|:-------------------|:------:| +| Apple TV | ✅ | +| Samsung TV (Tizen) | ✅ | +| LG TV (WebOS) | ✅ | +| Android TV | ❔ | +| Chromecast | ❔ | +| Roku | ❔ | +| Fire TV | ❔ | +| Nintendo Switch | ✅ | +| Xbox One/Series | ❔ | +| Playstation 4/5 | ❔ | -The last 5 videos' segments are cached to limit the number on queries on SponsorBlock and YouTube. +## Usage +Run iSponsorBlockTV on a computer that has network access. +Auto discovery will require the computer to be on the same network as the device during setup. +It connects to the device, watches its activity and skips any sponsor segment using the [SponsorBlock](https://sponsor.ajay.app/) API. +It can also skip/mute YouTube ads. -# Libraries used -- [pyatv](https://github.com/postlund/pyatv) Used to connect to the Apple TV +## Libraries used +- [pyytlounge](https://github.com/FabioGNR/pyytlounge) Used to interact with the device - asyncio and [aiohttp](https://github.com/aio-libs/aiohttp) - [async-cache](https://github.com/iamsinghrajat/async-cache) +- [Textual](https://github.com/textualize/textual/) Used for the amazing new graphical configurator +- [ssdp](https://github.com/codingjoe/ssdp) Used for auto discovery -# Projects using this proect +## Projects using this project - [Home Assistant Addon](https://github.com/bertybuttface/addons/tree/main/isponsorblocktv) -# Contributing - +## Contributing 1. Fork it () 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) @@ -35,9 +51,8 @@ The last 5 videos' segments are cached to limit the number on queries on Sponsor 5. Create a new Pull Request ## Contributors - - [dmunozv04](https://github.com/dmunozv04) - creator and maintainer - [HaltCatchFire](https://github.com/HaltCatchFire) - updated dependencies and improved skip logic - [Oxixes](https://github.com/oxixes) - added support for channel whitelist and minor improvements -# License +## License [![GNU GPLv3](https://www.gnu.org/graphics/gplv3-127x51.png)](https://www.gnu.org/licenses/gpl-3.0.en.html) diff --git a/iSponsorBlockTV/main.py b/iSponsorBlockTV/main.py index fd68b42..eb9fd29 100644 --- a/iSponsorBlockTV/main.py +++ b/iSponsorBlockTV/main.py @@ -8,6 +8,7 @@ import traceback class DeviceListener: def __init__(self, api_helper, config, screen_id, offset): + self.task: asyncio.Task = None self.api_helper = api_helper self.lounge_controller = ytlounge.YtLoungeApi(screen_id, config, api_helper) self.offset = offset @@ -53,12 +54,12 @@ class DeviceListener: await lounge_controller.connect() except: pass - # print(f"Connected to device {lounge_controller.screen_name}") + print(f"Connected to device {lounge_controller.screen_name}") try: - print("Subscribing to lounge") + #print("Subscribing to lounge") sub = await lounge_controller.subscribe_monitored(self) await sub - print("Subscription ended") + #print("Subscription ended") except: pass @@ -77,9 +78,10 @@ class DeviceListener: segments = [] if state.videoId: segments = await self.api_helper.get_segments(state.videoId) - print(segments) - if state.state.value == 1 and segments: # Playing and has segments to skip - await self.time_to_segment(segments, state.currentTime, time_start) + if state.state.value == 1: # Playing + print(f"Playing {state.videoId} with {len(segments)} segments") + if segments: # If there are segments + await self.time_to_segment(segments, state.currentTime, time_start) # Finds the next segment to skip to and skips to it async def time_to_segment(self, segments, position, time_start): @@ -108,13 +110,14 @@ class DeviceListener: self.api_helper.mark_viewed_segments(UUID) ) # Don't wait for this to finish + # Stops the connection to the device async def cancel(self): self.cancelled = True try: self.task.cancel() except Exception as e: - traceback.print_exc() + pass async def finish(devices): @@ -141,7 +144,6 @@ def main(config, debug): loop.run_forever() except KeyboardInterrupt as e: print("Keyboard interrupt detected, cancelling tasks and exiting...") - traceback.print_exc() loop.run_until_complete(finish(devices)) finally: loop.run_until_complete(web_session.close()) diff --git a/iSponsorBlockTV/ytlounge.py b/iSponsorBlockTV/ytlounge.py index ba06453..f0e3efe 100644 --- a/iSponsorBlockTV/ytlounge.py +++ b/iSponsorBlockTV/ytlounge.py @@ -63,12 +63,15 @@ class YtLoungeApi(pyytlounge.YtLoungeApi): self._update_state() # Unmute when the video starts playing if self.mute_ads and data.get("state", "0") == "1": + #print("Ad has ended, unmuting") create_task(self.mute(False, override=True)) elif self.mute_ads and event_type == "onAdStateChange": data = args[0] if data["adState"] == '0': # Ad is not playing + #print("Ad has ended, unmuting") create_task(self.mute(False, override=True)) else: # Seen multiple other adStates, assuming they are all ads + print("Ad has started, muting") create_task(self.mute(True, override=True)) # Manages volume, useful since YouTube wants to know the volume when unmuting (even if they already have it) elif event_type == "onVolumeChanged": @@ -77,6 +80,7 @@ class YtLoungeApi(pyytlounge.YtLoungeApi): # Gets segments for the next video before it starts playing elif event_type == "autoplayUpNext": if len(args) > 0 and (vid_id := args[0]["videoId"]): # if video id is not empty + print(f"Getting segments for next video: {vid_id}") create_task(self.api_helper.get_segments(vid_id)) # #Used to know if an ad is skippable or not @@ -84,6 +88,7 @@ class YtLoungeApi(pyytlounge.YtLoungeApi): data = args[0] # Gets segments for the next video (after the ad) before it starts playing if vid_id := data["contentVideoId"]: + print(f"Getting segments for next video: {vid_id}") create_task(self.api_helper.get_segments(vid_id)) if data["isSkippable"] == "true": # YouTube uses strings for booleans diff --git a/main_tui.py b/main_tui.py index 5a417f0..4b7a9af 100644 --- a/main_tui.py +++ b/main_tui.py @@ -1,5 +1,5 @@ from iSponsorBlockTV import setup_wizard from iSponsorBlockTV.helpers import Config -config = Config("config.json") +config = Config("data/config.json") setup_wizard.main(config) \ No newline at end of file