Skip to content

Commit

Permalink
ani-skip support out of WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
Commandcracker committed May 26, 2024
1 parent 99f296e commit 9e091a4
Show file tree
Hide file tree
Showing 12 changed files with 139 additions and 359 deletions.
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,9 @@ termux-setup-storage
- [x] Descriptions
- [x] Watching
- [x] Automatically start next episode
- [x] Discord Presence **WIP**
- [x] Discord Presence **Very WIP**
- [MPV] only
- [X] [ani-skip](https://github.com/synacktraa/ani-skip) support **Very WIP**
- [X] [ani-skip](https://github.com/synacktraa/ani-skip) support
- [x] [Syncplay](https://github.com/Syncplay/syncplay) support (almost out of WIP)
- [ ] Remember watch time **WIP**
- [ ] Remember completed Episodes (and series)
Expand Down Expand Up @@ -205,7 +205,6 @@ Place your custom CSS in `user_config_path("gucken").joinpath("custom.css")` and
- [ ] Proper error handling
- [ ] Logging and Crash reports
- [ ] Pre-fetching
- [ ] improve [ani-skip](https://github.com/synacktraa/ani-skip) support
- [ ] Use something like opencv to time match a sub from aniworld with a high quality video form another site.
- [ ] Image preview (Kitty protocol, iterm protocol, Sixel, textual-web)
- [ ] Support textual-web
Expand Down
6 changes: 4 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ maintainers = [{name="Commandcracker"}]
license = {file = "LICENSE.txt"}
readme = "README.md"
dependencies = [
"textual>=0.62.0",
"textual>=0.63.3",
"beautifulsoup4>=4.12.3",
"httpx>=0.27.0",
"pypresence>=4.3.0",
"packaging>=24.0",
"platformdirs>=4.2.2",
"toml>=0.10.2"
"toml>=0.10.2",
"fuzzywuzzy>=0.18.0",
"levenshtein>=0.25.1"
#"yt-dlp>=2024.4.9",
#"mpv>=1.0.6",
#"httpx[socks]",
Expand Down
2 changes: 1 addition & 1 deletion src/gucken/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.1.8"
__version__ = "0.1.9"
111 changes: 41 additions & 70 deletions src/gucken/aniskip.py
Original file line number Diff line number Diff line change
@@ -1,114 +1,85 @@
from difflib import SequenceMatcher
from tempfile import NamedTemporaryFile
from typing import Union
from dataclasses import dataclass

from fuzzywuzzy import process
from httpx import AsyncClient

from .tracker.myanimelist import search
from .rome import replace_roman_numerals

# TODO: improve fuzzy


def fuzzy_search(pattern, possibilities, threshold=0.6):
matches = []
for word in possibilities:
ratio = SequenceMatcher(None, pattern, word).ratio()
if ratio >= threshold:
matches.append((word, ratio))
return matches


def fuzzy_sort(pattern, possibilities):
return sorted(
possibilities,
key=lambda x: SequenceMatcher(None, pattern, x).ratio(),
reverse=True,
)
@dataclass
class SkipTimes:
op_start: float
op_end: float
ed_start: float
ed_end: float


async def get_timings_from_id(
anime_id: int, episode_number: int
) -> Union[dict[str, float], None]:
async with AsyncClient(verify=False) as client:
) -> Union[SkipTimes, None]:
async with (AsyncClient(verify=False) as client):
response = await client.get(
f"https://api.aniskip.com/v1/skip-times/{anime_id}/{episode_number}?types=op&types=ed"
)
json = response.json()
if json.get("found") is not True:
return None
op_start_time = 0
op_end_time = 0
ed_start_time = 0
ed_end_time = 0
return
op_start = 0
op_end = 0
ed_start = 0
ed_end = 0
for result in json["results"]:
skip_type = result["skip_type"]
start_time = result["interval"]["start_time"]
end_time = result["interval"]["end_time"]
if skip_type == "op":
op_start_time = start_time
op_end_time = end_time
op_start = start_time
op_end = end_time
if skip_type == "ed":
ed_start_time = start_time
ed_end_time = end_time
return {
"op_start_time": float(op_start_time),
"op_end_time": float(op_end_time),
"ed_start_time": float(ed_start_time),
"ed_end_time": float(ed_end_time),
}
ed_start = start_time
ed_end = end_time
return SkipTimes(
op_start=float(op_start),
op_end=float(op_end),
ed_start=float(ed_start),
ed_end=float(ed_end)
)


async def get_timings_from_search(
keyword: str, episode_number: int
) -> Union[dict[str, float], None]:
# TODO: improve search
) -> Union[SkipTimes, None]:
myanimelist_search_result = await search(keyword)
animes = {}
for anime in myanimelist_search_result["categories"][0]["items"]:
animes[anime["name"]] = anime["id"]
search_results = fuzzy_search(keyword, animes)
if len(search_results) > 0:
name = search_results[0][0]
anime_id = animes[name]
animes[anime["id"]] = replace_roman_numerals(anime["name"])
search_result = process.extractOne(replace_roman_numerals(keyword), animes, score_cutoff=50)
if search_result is not None:
anime_id = search_result[2]
return await get_timings_from_id(anime_id, episode_number)
return None


def opening_timings_to_mpv_option(timings=dict[str, float]) -> str:
op_start_time = timings["op_start_time"]
op_end_time = timings["op_end_time"]
return f"--script-opts-add=skip-op_start={op_start_time},skip-op_end={op_end_time}"


def ending_timings_to_mpv_option(timings=dict[str, float]) -> str:
ed_start_time = timings["ed_start_time"]
ed_end_time = timings["ed_end_time"]
return f"--script-opts-add=skip-ed_start={ed_start_time},skip-ed_end={ed_end_time}"


def chapter(start: float, end: float, title: str) -> str:
return f"\n[CHAPTER]\nTIMEBASE=1/1000\nSTART={int(start * 1000)}\nEND={int(end * 1000)}\nTITLE={title}\n"


def get_chapters_file_content(timings=dict[str, float]) -> str:
op_start_time = timings["op_start_time"]
op_end_time = timings["op_end_time"]
ed_start_time = timings["ed_start_time"]
ed_end_time = timings["ed_end_time"]
return (
";FFMETADATA1"
+ chapter(op_start_time, op_end_time, "Opening")
+ chapter(ed_start_time, ed_end_time, "Ending")
+ chapter(op_end_time, ed_start_time, "Episode")
)
def get_chapters_file_content(timings: SkipTimes) -> str:
string_builder = [";FFMETADATA1"]
if timings.op_start != timings.op_end:
string_builder.append(chapter(timings.op_start, timings.op_end, "Opening"))
if timings.ed_start != timings.ed_end:
string_builder.append(chapter(timings.ed_start, timings.ed_end, "Ending"))
if timings.op_end != 0 and timings.ed_start != 0:
string_builder.append(chapter(timings.op_end, timings.ed_start, "Episode"))
return "".join(string_builder)


def generate_chapters_file(timings=dict[str, float]) -> NamedTemporaryFile:
def generate_chapters_file(timings: SkipTimes) -> NamedTemporaryFile:
temp_file = NamedTemporaryFile(mode="w", prefix="gucken-", delete=False)
temp_file.write(get_chapters_file_content(timings))
temp_file.close()
return temp_file


def get_chapters_file_mpv_option(path: str) -> str:
return f"--chapters-file={path}"
61 changes: 25 additions & 36 deletions src/gucken/gucken.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import warnings
warnings.filterwarnings('ignore', message='Using slow pure-python SequenceMatcher. Install python-Levenshtein to remove this warning')

import argparse
import logging
from asyncio import gather
Expand Down Expand Up @@ -25,7 +28,6 @@
Checkbox,
Collapsible,
DataTable,
Footer,
Header,
Input,
Label,
Expand All @@ -40,10 +42,7 @@

from .aniskip import (
generate_chapters_file,
get_chapters_file_mpv_option,
get_timings_from_search,
opening_timings_to_mpv_option,
ending_timings_to_mpv_option
get_timings_from_search
)
from .custom_widgets import SortableTable
from .hoster._hosters import hoster
Expand Down Expand Up @@ -481,6 +480,7 @@ async def lookup_anime(self, keyword: str) -> None:
final_results.append(e)

# TODO: Sort final_results with fuzzy-sort
# from fuzzywuzzy import process process.extract()
if len(final_results) > 0:
self.current = final_results
for series in final_results:
Expand Down Expand Up @@ -681,7 +681,7 @@ async def update():

if ani_skip_opening or ani_skip_ending or ani_skip_chapters:
timings = await get_timings_from_search(
series_search_result.name, index + 1
series_search_result.name + " " + str(episode.season), episode.episode_number
)
if timings:
if isinstance(_player, MPVPlayer):
Expand All @@ -695,54 +695,42 @@ def delete_chapters_file():
pass

register_atexit(delete_chapters_file)
args.append(f"--chapters-file={chapters_file.name}")

args.append(get_chapters_file_mpv_option(chapters_file.name))

script_opts = []
if ani_skip_opening:
args.append(opening_timings_to_mpv_option(timings))

script_opts.append(f"skip-op_start={timings.op_start}")
script_opts.append(f"skip-op_end={timings.op_end}")
if ani_skip_ending:
args.append(ending_timings_to_mpv_option(timings))
script_opts.append(f"skip-ed_start={timings.ed_start}")
script_opts.append(f"skip-ed_end={timings.ed_end}")
if len(script_opts) > 0:
args.append(f"--script-opts={','.join(script_opts)}")

args.append("--script=" + str(Path(__file__).parent.joinpath("resources", "mpv_skip.lua")))
args.append("--scripts-append=" + str(Path(__file__).parent.joinpath("resources", "mpv_gucken.lua")))

if isinstance(_player, VLCPlayer):
# cant use --lua-config because it would override syncplay cfg
# cant use --extraintf and --lua-intf because it is already used by syncplay
"""
args = [
"vlc",
"--extraintf=luaintf",
"--lua-intf=skip",
"--lua-config=skip={" + f"op_start={op_start},op_end={op_end},ed_start={ed_start},ed_end={ed_end}" +"}",
url
]
"""
prepend_data = ["-- Generated"]

prepend_data = []
if ani_skip_opening:
prepend_data.append(set_default_vlc_interface_cfg("op_start", timings["op_start_time"]))
prepend_data.append(set_default_vlc_interface_cfg("op_end", timings["op_end_time"]))

prepend_data.append(set_default_vlc_interface_cfg("op_start", timings.op_start))
prepend_data.append(set_default_vlc_interface_cfg("op_end", timings.op_end))
if ani_skip_ending:
prepend_data.append(set_default_vlc_interface_cfg("ed_start", timings["ed_start_time"]))
prepend_data.append(set_default_vlc_interface_cfg("ed_end", timings["ed_end_time"]))

prepend_data.append("-- Generated\n")
prepend_data.append(set_default_vlc_interface_cfg("ed_start", timings.ed_start))
prepend_data.append(set_default_vlc_interface_cfg("ed_end", timings.ed_end))

vlc_intf_user_path = get_vlc_intf_user_path(_player.executable).vlc_intf_user_path
Path(vlc_intf_user_path).mkdir(mode=0o755, parents=True, exist_ok=True)

vlc_skip_plugin = Path(__file__).parent.joinpath("resources", "vlc_skip.lua")
copyTo = join(vlc_intf_user_path, "vlc_skip.lua")
vlc_skip_plugin = Path(__file__).parent.joinpath("resources", "vlc_gucken.lua")
copy_to = join(vlc_intf_user_path, "vlc_gucken.lua")

with open(vlc_skip_plugin, 'r') as f:
original_content = f.read()

with open(copyTo, 'w') as f:
with open(copy_to, 'w') as f:
f.write("\n".join(prepend_data) + original_content)

args.append("--control=luaintf{intf=vlc_skip}")
args.append("--control=luaintf{intf=vlc_gucken}")

if syncplay:
# TODO: make work with flatpak
Expand Down Expand Up @@ -771,6 +759,7 @@ def delete_chapters_file():
syncplay_path,
"--player-path",
player_path,
# "--debug",
url,
"--",
] + args
Expand Down
6 changes: 5 additions & 1 deletion src/gucken/resources/gucken.css
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Next > Container > Horizontal > Button {

#markdown {
margin: 0 0;
margin-top: -1;
margin-top: -2;
}

/*TODO: make height 100 of what the table needs, so there is only one scroll*/
Expand Down Expand Up @@ -67,4 +67,8 @@ ClickableListItem {
height: 1;
}

SortableTable {
width: auto;
}

/*$accent: lime;*/
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ local mpv_utils = require("mp.utils")

-- Stop script if skip.lua is inside scripts folder
local scripts_dir = mp.find_config_file("scripts")
if mpv_utils.file_info(mpv_utils.join_path(scripts_dir, "skip.lua")) ~= nil then
if scripts_dir ~= nil and mpv_utils.file_info(mpv_utils.join_path(scripts_dir, "skip.lua")) ~= nil then
mp.msg.info("Disabling, another skip.lua is already present in scripts dir")
return
end
Expand Down
Loading

0 comments on commit 9e091a4

Please sign in to comment.