Skip to content

Instantly share code, notes, and snippets.

@Jessidhia
Last active December 15, 2015 05:39
Show Gist options
  • Save Jessidhia/5210521 to your computer and use it in GitHub Desktop.
Save Jessidhia/5210521 to your computer and use it in GitHub Desktop.
#! /usr/bin/env ruby
require 'dm-core'
require 'unicode'
DataMapper::Logger.new($stdout, :info)
DataMapper.setup(:default, "sqlite://#{ENV['HOME']}/.kadr/db")
module KADR
class List
def initialize(str)
@list = []
str.split(",").each do |token|
if token.match(/^([COSTP]?\d+)-([COSTP]?\d+)$/)
s = str2val($1)
e = str2val($2)
@list << Range.new(s, e)
else
@list << str2val(token)
end
end
end
def to_s
lst = []
@list.each do |token|
if token.is_a? Range
lst << "#{val2str(token.min)}-#{val2str(token.max)}"
else
lst << val2str(token)
end
end
return lst.join ","
end
def <<(str)
val = str2val(str)
unless self === val
# HACK: we don't actually have to keep the lists normalized
@list << val
end
self
end
def ===(str)
val = str2val(str)
@list.find {|t| t === val}
end
private
# God help us if an anime reaches 2**18 episodes
EPTYPE_MAP = {
:C => 2**18,
:O => 2**19,
:S => 2**20,
:T => 2**21,
:P => 2**22,
}
def str2val(str)
return str if str.is_a? Fixnum
if str.match /^([COSTP])(\d+)$/
EPTYPE_MAP[$1.to_sym] | $2.to_i
else
str.to_i
end
end
def val2str(val)
str = ""
EPTYPE_MAP.each do |k,v|
if (val & v) != 0
val &= ~v
str = k.to_s
break
end
end
return str + val.to_s
end
end
class KnownFile
include DataMapper::Resource
storage_names[:default] = 'known_files'
property :filename, Text, :lazy => false
property :ed2k, Text, :lazy => false, :key => true
property :size, Integer, :key => true
has 1, :file, :child_key => [ :ed2k, :size ]
has 1, :anime, :through => :file
has 1, :episode, :through => :file
end
class File
include DataMapper::Resource
storage_names[:default] = 'adbcache_file'
property :fid, Integer, :key => true
property :group_name, Text, :lazy => false
property :length, Integer
belongs_to :known_file, :child_key => [ :ed2k, :size ]
belongs_to :anime, :child_key => [ :aid ]
belongs_to :episode, :child_key => [ :eid ]
end
class Episode
include DataMapper::Resource
storage_names[:default] = 'episode'
property :eid, Integer, :key => true
property :number, String, :length => 10
belongs_to :anime, :child_key => [ :aid ]
has n, :files, :child_key => [ :eid ]
has n, :known_files, :through => :files
def watched?
self.anime.watched_list === self.number
end
def watched!
self.anime.watch! self.number
end
end
class Anime
include DataMapper::Resource
storage_names[:default] = 'anidb_mylist_anime'
property :aid, Integer, :key => true
property :title, Text, :field => 'anime_title', :lazy => false
property :episode_count, Integer, :field => 'episodes'
property :watched_eps, Text, :lazy => false
def watched_list
@kadr_watched_list ||= KADR::List.new self.watched_eps
end
def watch(number)
self.watched_list = (self.watched_list << number)
end
def watch!(number)
self.watch(number)
self.save
end
def watched_list=(list)
@kadr_watched_list = list
self.watched_eps = list.to_s
end
has n, :files, :child_key => [ :aid ]
has n, :episodes, :child_key => [ :aid ]
has n, :known_files, :through => :files
end
end
DataMapper.finalize
MPVFile = Struct.new(:pid, :fd, :info, :time, :watched)
mpv_files = []
mpv_files_lock = Mutex.new
Thread.abort_on_exception = true
Thread.new do
last_mpv = 0
while true
mpv_files_lock.synchronize do
mpv_files.reject! do |mpv|
begin
puts "" unless mpv.watched || last_mpv == 0 || mpv.pid == last_mpv
last_mpv = mpv.pid
fname = File.readlink("/proc/#{mpv.pid}/fd/#{mpv.fd}")
bname = Unicode::normalize_C(File.basename(fname).force_encoding("UTF-8"))
unless bname == mpv.info.filename
puts "#{mpv.info.anime.title} - #{mpv.info.episode.number}: playback ended"
last_mpv = 0
true
else
if !mpv.watched
diff = Time.now - mpv.time
print "\r#{mpv.info.anime.title} - #{mpv.info.episode.number}: #{sprintf "%.1f", diff.ceil * 100.0 / (mpv.info.file.length * 0.7).floor}% (#{diff.ceil}/#{(mpv.info.file.length * 0.7).floor}s)"
if diff.ceil >= (mpv.info.file.length * 0.7).floor
puts "" # add a newline after the previous 'print' call
puts "#{mpv.info.anime.title} - #{mpv.info.episode.number}: marking as watched"
mpv.info.episode.watched!
mpv.watched = true
last_mpv = 0
end
end
false
end
rescue Errno::ENOENT
puts "#{mpv.info.anime.title} - #{mpv.info.episode.number}: playback ended"
last_mpv = 0
true
end
end
end
sleep 1
end
end
while true
mpvs = `pgrep mpv`.split "\n"
mpvs.each do |pid|
begin
Dir.foreach("/proc/#{pid}/fd/") do |fd|
next if %w{. ..}.include? fd
begin
fname = File.readlink("/proc/#{pid}/fd/#{fd}")
stat = File.stat(fname)
next unless stat.file?
bname = Unicode::normalize_C(File.basename(fname).force_encoding("UTF-8"))
# duplicate
break if mpv_files.find {|mpv| bname == mpv.info.filename}
info = KADR::KnownFile.first(:filename => bname, :size => stat.size)
puts "#{info.anime.title} - #{info.episode.number}: mpv pid #{pid}, length #{info.file.length} seconds"
if info.file.length < 50
puts "#{info.anime.title} - #{info.episode.number}: file too short!! ignoring..."
break
end
watched = info.episode.watched?
if watched
puts "#{info.anime.title} - #{info.episode.number}: episode already watched"
else
puts "#{info.anime.title} - #{info.episode.number}: will mark as watched in #{(info.file.length * 0.7).floor} seconds"
end
mpv_files_lock.synchronize {mpv_files << MPVFile.new(pid, fd, info, Time.now, watched)}
# Only check the first file: any others are probably matroska linked segments, or --audiofile
break
rescue Errno::ENOENT
end
end
rescue Errno::ENOENT
end
end
sleep 5
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment