From 4c870b6908beffd7b88bcd51922939898e88a075 Mon Sep 17 00:00:00 2001 From: dmunozv04 <39565245+dmunozv04@users.noreply.github.com> Date: Mon, 11 Apr 2022 12:50:41 +0200 Subject: [PATCH 1/5] Add enviroment variable to check if running on docker --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index acd4cff..1c0bbab 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ FROM python:alpine RUN python -m venv /opt/venv -ENV PATH="/opt/venv/bin:$PATH" PIP_NO_CACHE_DIR=off +ENV PATH="/opt/venv/bin:$PATH" PIP_NO_CACHE_DIR=off iSPBTV_docker=True COPY requirements.txt . From fe9bd8a8d410aa66d0b9cbea595a8276d0463864 Mon Sep 17 00:00:00 2001 From: dmunozv04 <39565245+dmunozv04@users.noreply.github.com> Date: Mon, 11 Apr 2022 12:50:57 +0200 Subject: [PATCH 2/5] Move to folder --- create_config.py | 105 ----------------------------------------------- 1 file changed, 105 deletions(-) delete mode 100644 create_config.py diff --git a/create_config.py b/create_config.py deleted file mode 100644 index 26738df..0000000 --- a/create_config.py +++ /dev/null @@ -1,105 +0,0 @@ -import pyatv -import json -import asyncio -from pyatv.const import DeviceModel -from pyatv.scripts import TransformProtocol -import sys - -def load_config(config_file="config.json"): - try: - with open(config_file) as f: - config = json.load(f) - except: - config = {} - return config - -def save_config(config, config_file="config.json"): - with open(config_file, "w") as f: - json.dump(config, f) - -#Taken from postlund/pyatv atvremote.py -async def _read_input(loop: asyncio.AbstractEventLoop, prompt: str): - sys.stdout.write(prompt) - sys.stdout.flush() - user_input = await loop.run_in_executor(None, sys.stdin.readline) - return user_input.strip() - -async def find_atvs(loop): - devices = await pyatv.scan(loop) - if not devices: - print("No devices found") - return - atvs = [] - for i in devices: - #Only get Apple TV's - if i.device_info.model in [DeviceModel.Gen4, DeviceModel.Gen4K, DeviceModel.AppleTV4KGen2]: - #if i.device_info.model in [DeviceModel.AppleTV4KGen2]: #FOR TESTING - if input("Found {}. Do you want to add it? (y/n) ".format(i.name)) == "y": - - identifier = i.identifier - - pairing = await pyatv.pair(i, loop=loop, protocol=pyatv.Protocol.AirPlay) - await pairing.begin() - if pairing.device_provides_pin: - pin = await _read_input(loop, "Enter PIN on screen: ") - pairing.pin(pin) - await pairing.finish() - creds = pairing.service.credentials - atvs.append({"identifier": identifier, "airplay_credentials": creds}) - print("Pairing successful") - await pairing.close() - return atvs - - - - -def main(): - config = load_config() - try: num_atvs = len(config["atvs"]) - except: num_atvs = 0 - if input("Found {} Apple TV(s) in config.json. Add more? (y/n) ".format(num_atvs)) == "y": - loop = asyncio.get_event_loop_policy().get_event_loop() - asyncio.set_event_loop(loop) - task = loop.create_task(find_atvs(loop)) - loop.run_until_complete(task) - atvs = task.result() - try: - for i in atvs: - config["atvs"].append(i) - print("done adding") - except: - print("rewriting atvs (don't worry if none were saved before)") - config["atvs"] = atvs - - try : apikey = config["apikey"] - except: - apikey = "" - if apikey != "" : - if input("Apikey already specified. Change it? (y/n) ") == "y": - apikey = input("Enter your API key: ") - config["apikey"] = apikey - else: - print("get youtube apikey here: https://developers.google.com/youtube/registering_an_application") - apikey = input("Enter your API key: ") - config["apikey"] = apikey - - try: skip_categories = config["skip_categories"] - except: - skip_categories = [] - - if skip_categories != []: - if input("Skip categories already specified. Change them? (y/n) ") == "y": - categories = input("Enter skip categories (space sepparated) Options: [sponsor, selfpromo, exclusive_access, interaction, poi_highlight, intro, outro, preview, filler, music_offtopic:\n") - skip_categories = categories.split(" ") - else: - categories = input("Enter skip categories (space sepparated) Options: [sponsor, selfpromo, exclusive_access, interaction, poi_highlight, intro, outro, preview, filler, music_offtopic:\n") - skip_categories = categories.split(" ") - config["skip_categories"] = skip_categories - print("config finished") - save_config(config) - - - -if __name__ == "__main__": - print("starting") - main() From c36c8dfa490e514607a34e0c627ed176d085559e Mon Sep 17 00:00:00 2001 From: dmunozv04 <39565245+dmunozv04@users.noreply.github.com> Date: Mon, 11 Apr 2022 21:40:15 +0200 Subject: [PATCH 3/5] Finish refactoring --- .DS_Store | Bin 0 -> 6148 bytes .gitignore | 1 - iSponsorBlockTV/__init__.py | 0 iSponsorBlockTV/config_setup.py | 100 ++++++++++++++++++ iSponsorBlockTV/helpers.py | 46 ++++++++ iSponsorBlockTV/macos_install.py | 46 ++++++++ iSponsorBlockTV/main.py | 172 ++++++++++++++++++++++++++++++ main-macos.py | 7 ++ main-macos.spec | 47 +++++++++ main.py | 174 +------------------------------ requirements.txt | 1 + 11 files changed, 421 insertions(+), 173 deletions(-) create mode 100644 .DS_Store create mode 100644 iSponsorBlockTV/__init__.py create mode 100644 iSponsorBlockTV/config_setup.py create mode 100644 iSponsorBlockTV/helpers.py create mode 100644 iSponsorBlockTV/macos_install.py create mode 100644 iSponsorBlockTV/main.py create mode 100644 main-macos.py create mode 100644 main-macos.spec diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..44ab99ecd83a17d507e75be292ea56ff7eb8b35a GIT binary patch literal 6148 zcmeHK!AiqG5S`Ujw-vDmL63X!)@(RnbDpSB9mu?K`*-r z;xriBYaE6F!@%S*KzFwU0dygP7>4ifOK=lMS#H~}Y-ZLf&CPRO;g!Y8nRk`;yxh;b zS<~;F(Com^uL5l!rfK|P`@vDv?wwbcchV&Hqom!D!BN=8EvLs(5~jUo+D*btp5v(n z&da=fUR~|?orYZ#8}+S0P4pXeve!2UgEC)PbGG+e*LU~*hr#2MVwK?&BxOnCJ3ON? zl;W+|N#ZoQL@z~H5Mt^X!_M5JyzHR0HeU#I?&V3=LR0scB8S$|^Voil?|z<|Cm zd|?W5H7Obf3= segment[0] and position < segment[1]): + next_segment = [position, segment[1]] + break + if segment[0] > position: + next_segment = segment + break + time_to_next = next_segment[0] - position + await skip(time_to_next, next_segment[1], rc) + +async def skip(time_to, position, rc): + await asyncio.sleep(time_to) + await rc.set_position(position) + + +async def connect_atv(loop, identifier, airplay_credentials): + """Find a device and print what is playing.""" + print("Discovering devices on network...") + atvs = await pyatv.scan(loop, identifier = identifier) + + if not atvs: + print("No device found, will retry") + return + + config = atvs[0] + config.set_credentials(pyatv.Protocol.AirPlay, airplay_credentials) + + print(f"Connecting to {config.address}") + return await pyatv.connect(config, loop) + + +async def loop_atv(event_loop, atv_config, apikey, categories, web_session): + identifier = atv_config["identifier"] + airplay_credentials = atv_config["airplay_credentials"] + atv = await connect_atv(event_loop, identifier, airplay_credentials) + if atv: + listener = MyPushListener(apikey, atv, categories, web_session) + + atv.push_updater.listener = listener + atv.push_updater.start() + print("Push updater started") + while True: + await asyncio.sleep(20) + try: + atv.metadata.app + except: + print("Reconnecting to Apple TV") + #reconnect to apple tv + atv = await connect_atv(event_loop, identifier, airplay_credentials) + if atv: + listener = MyPushListener(apikey, atv, categories, web_session) + + atv.push_updater.listener = listener + atv.push_updater.start() + print("Push updater started") + + + + + + +def main(atv_configs, apikey, categories, debug): + loop = asyncio.get_event_loop_policy().get_event_loop() + if debug: + loop.set_debug(True) + asyncio.set_event_loop(loop) + web_session = aiohttp.ClientSession() + for i in atv_configs: + loop.create_task(loop_atv(loop, i, apikey, categories, web_session)) + loop.run_forever() + + +if __name__ == "__main__": + print("starting") + main() diff --git a/main-macos.py b/main-macos.py new file mode 100644 index 0000000..2264d52 --- /dev/null +++ b/main-macos.py @@ -0,0 +1,7 @@ +from iSponsorBlockTV import helpers +import sys +import os + +if getattr(sys, 'frozen', False): + os.environ['SSL_CERT_FILE'] = os.path.join(sys._MEIPASS, 'lib', 'cert.pem') +helpers.app_start() \ No newline at end of file diff --git a/main-macos.spec b/main-macos.spec new file mode 100644 index 0000000..7f16839 --- /dev/null +++ b/main-macos.spec @@ -0,0 +1,47 @@ +# -*- mode: python ; coding: utf-8 -*- + +from PyInstaller.utils.hooks import exec_statement +cert_datas = exec_statement(""" + import ssl + print(ssl.get_default_verify_paths().cafile)""").strip().split() +cert_datas = [(f, 'lib') for f in cert_datas] + +block_cipher = None + +options = [ ('u', None, 'OPTION') ] + +a = Analysis(['main-macos.py'], + pathex=[], + binaries=[], + datas=cert_datas, + hiddenimports=['certifi'], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False) +pyz = PYZ(a.pure, a.zipped_data, + cipher=block_cipher) + +exe = EXE(pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + [], + options, + name='iSponsorBlockTV', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=True, + disable_windowed_traceback=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None) \ No newline at end of file diff --git a/main.py b/main.py index f030d5c..2ec464a 100644 --- a/main.py +++ b/main.py @@ -1,173 +1,3 @@ -import sys -import asyncio -import pyatv -import aiohttp -from cache import AsyncTTL -import json -import time +from iSponsorBlockTV import helpers -def listToTuple(function): - def wrapper(*args): - args = [tuple(x) if type(x) == list else x for x in args] - result = function(*args) - result = tuple(result) if type(result) == list else result - return result - return wrapper - -class MyPushListener(pyatv.interface.PushListener): - task = None - apikey = None - rc = None - - web_session = None - categories = ["sponsor"] - - def __init__(self, apikey, atv, categories, web_session): - self.apikey = apikey - self.rc = atv.remote_control - self.web_session = web_session - self.categories = categories - self.atv = atv - - - def playstatus_update(self, updater, playstatus): - try: - self.task.cancel() - except: - pass - time_start = time.time() - self.task = asyncio.create_task(process_playstatus(playstatus, self.apikey, self.rc, self.web_session, self.categories, self.atv, time_start)) - def playstatus_error(self, updater, exception): - print(exception) - print("stopped") - - - -async def process_playstatus(playstatus, apikey, rc, web_session, categories, atv, time_start): - if playstatus.device_state == playstatus.device_state.Playing and atv.metadata.app.identifier == "com.google.ios.youtube": - vid_id = await get_vid_id(playstatus.title, playstatus.artist, apikey, web_session) - print(vid_id) - segments, duration = await get_segments(vid_id, web_session, categories) - print(segments) - await time_to_segment(segments, playstatus.position, rc, time_start) - - -@AsyncTTL(time_to_live=300, maxsize=5) -async def get_vid_id(title, artist, api_key, web_session): - url = f"https://youtube.googleapis.com/youtube/v3/search?q={title} - {artist}&key={api_key}&maxResults=1" - async with web_session.get(url) as response: - response = await response.json() - vid_id = response["items"][0]["id"]["videoId"] - return vid_id - -@listToTuple -@AsyncTTL(time_to_live=300, maxsize=5) -async def get_segments(vid_id, web_session, categories = ["sponsor"]): - params = {"videoID": vid_id, - "category": categories, - "actionType": "skip", - "service": "youtube"} - headers = {'Accept': 'application/json'} - url = "https://sponsor.ajay.app/api/skipSegments" - async with web_session.get(url, headers = headers, params = params) as response: - response = await response.json() - segments = [] - try: - duration = response[0]["videoDuration"] - for i in response: - segment = i["segment"] - try: - #Get segment before to check if they are too close to each other - segment_before_end = segments[-1][1] - segment_before_start = segments[-1][0] - - except: - segment_before_end = -10 - if segment[0] - segment_before_end < 1: #Less than 1 second appart, combine them and skip them together - segment = [segment_before_start, segment[1]] - segments.pop() - segments.append(segment) - except: - duration = 0 - return segments, duration - - -async def time_to_segment(segments, position, rc, time_start): - position = position + (time.time() - time_start) - for segment in segments: - if position < 2 and (position >= segment[0] and position < segment[1]): - next_segment = [position, segment[1]] - break - if segment[0] > position: - next_segment = segment - break - time_to_next = next_segment[0] - position - await skip(time_to_next, next_segment[1], rc) - -async def skip(time_to, position, rc): - await asyncio.sleep(time_to) - await rc.set_position(position) - - -async def connect_atv(loop, identifier, airplay_credentials): - """Find a device and print what is playing.""" - print("Discovering devices on network...") - atvs = await pyatv.scan(loop, identifier = identifier) - - if not atvs: - print("No device found, will retry") - return - - config = atvs[0] - config.set_credentials(pyatv.Protocol.AirPlay, airplay_credentials) - - print(f"Connecting to {config.address}") - return await pyatv.connect(config, loop) - - -async def loop_atv(event_loop, atv_config, apikey, categories, web_session): - identifier = atv_config["identifier"] - airplay_credentials = atv_config["airplay_credentials"] - atv = await connect_atv(event_loop, identifier, airplay_credentials) - if atv: - listener = MyPushListener(apikey, atv, categories, web_session) - - atv.push_updater.listener = listener - atv.push_updater.start() - print("Push updater started") - while True: - await asyncio.sleep(20) - try: - atv.metadata.app - except: - print("Reconnecting to Apple TV") - #reconnect to apple tv - atv = await connect_atv(event_loop, identifier, airplay_credentials) - if atv: - listener = MyPushListener(apikey, atv, categories, web_session) - - atv.push_updater.listener = listener - atv.push_updater.start() - print("Push updater started") - - - - - -def load_config(config_file="config.json"): - with open(config_file) as f: - config = json.load(f) - return config["atvs"], config["apikey"], config["skip_categories"] - -def start_async(): - loop = asyncio.get_event_loop_policy().get_event_loop() - asyncio.set_event_loop(loop) - atv_configs, apikey, categories = load_config() - web_session = aiohttp.ClientSession() - for i in atv_configs: - loop.create_task(loop_atv(loop, i, apikey, categories, web_session)) - loop.run_forever() - -if __name__ == "__main__": - print("starting") - start_async() +helpers.app_start() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 95d8e05..1457d11 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ pyatv aiohttp aiodns async-cache +argparse From d5a3ed641b6b80032a47e5cf3fdaab56d6023bc4 Mon Sep 17 00:00:00 2001 From: dmunozv04 <39565245+dmunozv04@users.noreply.github.com> Date: Mon, 11 Apr 2022 21:48:38 +0200 Subject: [PATCH 4/5] Cahnge binary path --- iSponsorBlockTV/macos_install.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iSponsorBlockTV/macos_install.py b/iSponsorBlockTV/macos_install.py index 476c090..7867568 100644 --- a/iSponsorBlockTV/macos_install.py +++ b/iSponsorBlockTV/macos_install.py @@ -13,7 +13,7 @@ default_plist = {"Label": "com.dmunozv04iSponsorBlockTV", } def create_plist(path): plist = default_plist - plist["ProgramArguments"] = [path + "/iSponsorBlockTV"] + plist["ProgramArguments"] = [path + "/iSponsorBlockTV-macos"] plist["StandardErrorPath"] = path + "/iSponsorBlockTV.error.log" plist["StandardOutPath"] = path + "/iSponsorBlockTV.out.log" plist["WorkingDirectory"] = path @@ -29,7 +29,7 @@ def run_setup(file): def main(): correct_path = os.path.expanduser("~/iSponsorBlockTV") - if os.path.isfile(correct_path + "/iSponsorBlockTV"): + if os.path.isfile(correct_path + "/iSponsorBlockTV-macos"): print("Program is on the right path") print("The launch daemon will now be installed") create_plist(correct_path) From 713554e04d68aa218a4f91c87419b68d76b1b411 Mon Sep 17 00:00:00 2001 From: dmunozv04 <39565245+dmunozv04@users.noreply.github.com> Date: Mon, 11 Apr 2022 21:54:49 +0200 Subject: [PATCH 5/5] change filename --- iSponsorBlockTV/macos_install.py | 2 +- main-macos.spec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/iSponsorBlockTV/macos_install.py b/iSponsorBlockTV/macos_install.py index 7867568..f1a963c 100644 --- a/iSponsorBlockTV/macos_install.py +++ b/iSponsorBlockTV/macos_install.py @@ -1,6 +1,6 @@ import plistlib import os -from . import helpers, config_setup +from . import config_setup default_plist = {"Label": "com.dmunozv04iSponsorBlockTV", "RunAtLoad": True, diff --git a/main-macos.spec b/main-macos.spec index 7f16839..f0ee8d9 100644 --- a/main-macos.spec +++ b/main-macos.spec @@ -33,7 +33,7 @@ exe = EXE(pyz, a.datas, [], options, - name='iSponsorBlockTV', + name='iSponsorBlockTV-macos', debug=False, bootloader_ignore_signals=False, strip=False,