Last active
December 15, 2015 05:39
-
-
Save Jessidhia/5210521 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /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