Skip to content

Commit

Permalink
feat: Ignore the previously skipped segment for a duration
Browse files Browse the repository at this point in the history
This is useful if you want to to go back and watch a segment.
Also fixes #18 occasional dupliated log lines.
  • Loading branch information
gabe565 committed Mar 10, 2024
1 parent 781c757 commit 0303a25
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 30 deletions.
31 changes: 16 additions & 15 deletions docs/castsponsorskip.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,21 @@ castsponsorskip [flags]
### Options

```
--action-types strings SponsorBlock action types to handle. Shorter segments that overlap with content can be muted instead of skipped. (default [skip,mute])
-c, --categories strings Comma-separated list of SponsorBlock categories to skip (default [sponsor])
--completion string Output command-line completion code for the specified shell. Can be 'bash', 'zsh', 'fish', or 'powershell'.
--devices strings Comma-separated list of device addresses. This will disable discovery and is not recommended unless discovery fails
--discover-interval duration Interval to restart the DNS discovery client (default 5m0s)
-h, --help help for castsponsorskip
--log-level string Log level (debug, info, warn, error) (default "info")
--mute-ads Mutes the device while an ad is playing (default true)
-i, --network-interface string Network interface to use for multicast dns discovery. (default all interfaces)
--paused-interval duration Interval to scan paused devices (default 1m0s)
--playing-interval duration Interval to scan playing devices (default 500ms)
--skip-delay duration Delay skipping the start of a segment
--skip-sponsors Skip sponsored segments with SponsorBlock (default true)
-v, --version version for castsponsorskip
--youtube-api-key string YouTube API key for fallback video identification (required on some Chromecast devices).
--action-types strings SponsorBlock action types to handle. Shorter segments that overlap with content can be muted instead of skipped. (default [skip,mute])
-c, --categories strings Comma-separated list of SponsorBlock categories to skip (default [sponsor])
--completion string Output command-line completion code for the specified shell. Can be 'bash', 'zsh', 'fish', or 'powershell'.
--devices strings Comma-separated list of device addresses. This will disable discovery and is not recommended unless discovery fails
--discover-interval duration Interval to restart the DNS discovery client (default 5m0s)
-h, --help help for castsponsorskip
--ignore-segment-duration duration Ignores the previous sponsored segment for a set amount of time. Useful if you want to to go back and watch a segment. (default 1m0s)
--log-level string Log level (debug, info, warn, error) (default "info")
--mute-ads Mutes the device while an ad is playing (default true)
-i, --network-interface string Network interface to use for multicast dns discovery. (default all interfaces)
--paused-interval duration Interval to scan paused devices (default 1m0s)
--playing-interval duration Interval to scan playing devices (default 500ms)
--skip-delay duration Delay skipping the start of a segment
--skip-sponsors Skip sponsored segments with SponsorBlock (default true)
-v, --version version for castsponsorskip
--youtube-api-key string YouTube API key for fallback video identification (required on some Chromecast devices).
```

23 changes: 13 additions & 10 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ func Reset() {
Default = Config{
LogLevel: "info",

DiscoverInterval: 5 * time.Minute,
PausedInterval: time.Minute,
PlayingInterval: 500 * time.Millisecond,
SkipDelay: 0,
DiscoverInterval: 5 * time.Minute,
PausedInterval: time.Minute,
PlayingInterval: 500 * time.Millisecond,
SkipDelay: 0,
IgnoreSegmentDuration: time.Minute,

NetworkInterface: nil,

Expand All @@ -44,12 +45,13 @@ type Config struct {

LogLevel string `mapstructure:"log-level"`

DeviceAddrStrs []string `mapstructure:"devices"`
DeviceAddrs []castdns.CastEntry `mapstructure:"-"`
DiscoverInterval time.Duration `mapstructure:"discover-interval"`
PausedInterval time.Duration `mapstructure:"paused-interval"`
PlayingInterval time.Duration `mapstructure:"playing-interval"`
SkipDelay time.Duration `mapstructure:"skip-delay"`
DeviceAddrStrs []string `mapstructure:"devices"`
DeviceAddrs []castdns.CastEntry `mapstructure:"-"`
DiscoverInterval time.Duration `mapstructure:"discover-interval"`
PausedInterval time.Duration `mapstructure:"paused-interval"`
PlayingInterval time.Duration `mapstructure:"playing-interval"`
SkipDelay time.Duration `mapstructure:"skip-delay"`
IgnoreSegmentDuration time.Duration `mapstructure:"ignore-segment-duration"`

NetworkInterfaceName string `mapstructure:"network-interface"`
NetworkInterface *net.Interface
Expand All @@ -71,6 +73,7 @@ func (c *Config) RegisterFlags(cmd *cobra.Command) {
c.RegisterPausedInterval(cmd)
c.RegisterPlayingInterval(cmd)
c.RegisterSkipDelay(cmd)
c.RegisterSegmentIgnore(cmd)
c.RegisterSkipSponsors(cmd)
c.RegisterCategories(cmd)
c.RegisterActionTypes(cmd)
Expand Down
13 changes: 13 additions & 0 deletions internal/config/intervals.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,16 @@ func (c *Config) RegisterSkipDelay(cmd *cobra.Command) {
panic(err)
}
}

func (c *Config) RegisterSegmentIgnore(cmd *cobra.Command) {
key := "ignore-segment-duration"
cmd.PersistentFlags().Duration(key, Default.IgnoreSegmentDuration, "Ignores the previous sponsored segment for a set amount of time. Useful if you want to to go back and watch a segment.")
if err := c.viper.BindPFlag(key, cmd.PersistentFlags().Lookup(key)); err != nil {
panic(err)
}
if err := cmd.RegisterFlagCompletionFunc(key, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return []string{"30s", "1m", "2m", "5m"}, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveKeepOrder
}); err != nil {
panic(err)
}
}
27 changes: 22 additions & 5 deletions internal/device/watch.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ const (
StateIdle = "IDLE"
StateAd = 1081

NoMutedSegment = -1
NoMutedSegment = -1
NoSkippedSegment = -1
)

var (
Expand All @@ -47,10 +48,12 @@ type Device struct {
tickInterval time.Duration
ticker *time.Ticker

state string
meta VideoMeta
segments []sponsorblock.Segment
mutedSegmentId int
state string
meta VideoMeta
segments []sponsorblock.Segment
prevSegmentIdx int
prevSegmentIgnore time.Time
mutedSegmentId int
}

func NewDevice(entry castdns.CastEntry, opts ...Option) *Device {
Expand Down Expand Up @@ -85,6 +88,7 @@ func NewDevice(entry castdns.CastEntry, opts ...Option) *Device {
entry: entry,
logger: logger,
mutedSegmentId: NoMutedSegment,
prevSegmentIdx: NoSkippedSegment,
}

for _, opt := range opts {
Expand Down Expand Up @@ -201,6 +205,7 @@ func (d *Device) tick() error {

if d.meta.CurrVideoId != d.meta.PrevVideoId {
d.segments = nil
d.prevSegmentIdx = NoSkippedSegment
if d.meta.CurrVideoId != "" {
d.logger.Info("Detected video stream.", "video_id", d.meta.CurrVideoId)
d.meta.PrevVideoId = d.meta.CurrVideoId
Expand Down Expand Up @@ -304,6 +309,7 @@ func (d *Device) onMessage(msg *api.CastMessage) {
case "CLOSE":
d.unmuteSegment()
d.segments = nil
d.prevSegmentIdx = NoSkippedSegment
d.meta.Clear()
}
}
Expand Down Expand Up @@ -337,6 +343,7 @@ func (d *Device) queryVideoId() {
d.meta.PrevTitle = d.meta.CurrTitle
d.unmuteSegment()
d.segments = nil
d.prevSegmentIdx = NoSkippedSegment

if config.Default.YouTubeAPIKey == "" {
d.logger.Error("Video ID not set. Please configure a YouTube API key.")
Expand Down Expand Up @@ -393,12 +400,22 @@ func (d *Device) handleSegment(castMedia *cast.Media, castVol *cast.Volume, segm
to := time.Duration(segment.Segment[1]) * time.Second
switch segment.ActionType {
case sponsorblock.ActionTypeSkip:
if i == d.prevSegmentIdx {
if now := time.Now(); now.Before(d.prevSegmentIgnore) {
d.logger.Debug("Ignoring segment.", "category", segment.Category, "from", from, "to", to, "until", d.prevSegmentIgnore.Truncate(time.Second).String())
d.prevSegmentIgnore = now.Add(config.Default.IgnoreSegmentDuration)
return
}
}

d.logger.Info("Skipping to timestamp.", "category", segment.Category, "from", from, "to", to)
// Cast API seems to ignore decimals, so add 100ms to seek time in case sponsorship ends at 0.9 seconds.
if err := d.app.SeekToTime(segment.Segment[1] + 0.1); err != nil {
d.logger.Warn("Failed to seek to timestamp.", "to", segment.Segment[1], "error", err.Error())
}
castMedia.CurrentTime = segment.Segment[1]
d.prevSegmentIdx = i
d.prevSegmentIgnore = time.Now().Add(config.Default.IgnoreSegmentDuration)
case sponsorblock.ActionTypeMute:
if !castVol.Muted || i != d.mutedSegmentId {
d.logger.Info("Mute segment.", "category", segment.Category, "from", from, "to", to)
Expand Down

0 comments on commit 0303a25

Please sign in to comment.