├── .gitignore
├── img
├── fumi.png
├── kei.png
├── other.png
├── yui.png
├── aikawa.png
├── finish.png
├── okasan.png
├── yukari.png
└── yuzuko.png
├── templates
├── finish.html
├── base.html
├── main.js
├── index.html
├── tag.html
└── lib
│ └── jquery.hotkeys.js
├── infinity
├── template.html
├── search.html
├── index.cgi
├── index2.cgi
├── search.cgi
└── json2sqlite.py
├── README.md
├── LICENSE
└── yuyushiki.py
/.gitignore:
--------------------------------------------------------------------------------
1 | .pyc
2 | .pyo
3 | *.un~
4 | .DS_Store
5 | data
6 | yuyushiki.db
7 |
--------------------------------------------------------------------------------
/img/fumi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/non117/yuyushiki/HEAD/img/fumi.png
--------------------------------------------------------------------------------
/img/kei.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/non117/yuyushiki/HEAD/img/kei.png
--------------------------------------------------------------------------------
/img/other.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/non117/yuyushiki/HEAD/img/other.png
--------------------------------------------------------------------------------
/img/yui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/non117/yuyushiki/HEAD/img/yui.png
--------------------------------------------------------------------------------
/img/aikawa.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/non117/yuyushiki/HEAD/img/aikawa.png
--------------------------------------------------------------------------------
/img/finish.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/non117/yuyushiki/HEAD/img/finish.png
--------------------------------------------------------------------------------
/img/okasan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/non117/yuyushiki/HEAD/img/okasan.png
--------------------------------------------------------------------------------
/img/yukari.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/non117/yuyushiki/HEAD/img/yukari.png
--------------------------------------------------------------------------------
/img/yuzuko.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/non117/yuyushiki/HEAD/img/yuzuko.png
--------------------------------------------------------------------------------
/templates/finish.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block extrahead %}
3 |
9 | {% endblock %}
10 | {% block content %}
11 |
12 |
13 |
14 |
15 |
進捗
16 |
100 % (100%)
17 |
18 | {% endblock %}
19 |
20 |
--------------------------------------------------------------------------------
/infinity/template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ゆゆ式
5 |
18 |
19 |
20 | {% for frame in frames %}
21 |
22 | {% endfor %}
23 |
24 |
25 |
--------------------------------------------------------------------------------
/infinity/search.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ゆゆ式
5 |
18 |
19 |
20 | {{ count }}件
21 | {% for result in results %}
22 |
23 | {% endfor %}
24 |
25 |
26 |
--------------------------------------------------------------------------------
/templates/base.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Yuyushiki Annotation Tool
6 |
20 |
21 | {% block extrahead %}{% endblock %}
22 |
23 |
24 | Yuyushiki Annotation Tool
25 |
26 | {% block content %}{% endblock %}
27 |
28 |
29 |
--------------------------------------------------------------------------------
/infinity/index.cgi:
--------------------------------------------------------------------------------
1 | #! /usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | import sqlite3
4 | from functools import reduce
5 | from jinja2 import Environment, FileSystemLoader
6 |
7 | conn = sqlite3.connect('yuyushiki.db', check_same_thread=False)
8 |
9 | def get4frame():
10 | sql = 'select path from comics order by random() limit 4'
11 | cursor = conn.cursor()
12 | l = cursor.execute(sql).fetchall()
13 | cursor.close()
14 | return reduce(lambda x, y: x+y,l)
15 |
16 | def index():
17 | env = Environment(loader=FileSystemLoader('./', encoding='utf8'))
18 | tpl = env.get_template('template.html')
19 | frames = get4frame()
20 | html = tpl.render({'frames':frames})
21 | print('Content-Type: text/html; charset=utf-8\n')
22 | print(html.encode('utf-8'))
23 |
24 | if __name__ == '__main__':
25 | index()
26 |
27 |
--------------------------------------------------------------------------------
/infinity/index2.cgi:
--------------------------------------------------------------------------------
1 | #! /usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | import sqlite3
4 | from jinja2 import Environment, FileSystemLoader
5 |
6 | conn = sqlite3.connect('yuyushiki.db', check_same_thread=False)
7 |
8 | def get4frame():
9 | sql = 'select path from comics where frameno={0} order by random() limit 1'
10 | cursor = conn.cursor()
11 | res = []
12 | for i in range(4):
13 | l = cursor.execute(sql.format(i+1)).fetchall()
14 | res.append(l[0][0])
15 | cursor.close()
16 | return res
17 |
18 | def index():
19 | env = Environment(loader=FileSystemLoader('./', encoding='utf8'))
20 | tpl = env.get_template('template.html')
21 | frames = get4frame()
22 | html = tpl.render({'frames':frames})
23 | print('Content-Type: text/html; charset=utf-8\n')
24 | print(html.encode('utf-8'))
25 |
26 | if __name__ == '__main__':
27 | index()
28 |
29 |
--------------------------------------------------------------------------------
/infinity/search.cgi:
--------------------------------------------------------------------------------
1 | #! /usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | import cgi
4 | import sqlite3
5 | from functools import reduce
6 | from jinja2 import Environment, FileSystemLoader
7 |
8 | conn = sqlite3.connect('yuyushiki.db', check_same_thread=False)
9 |
10 | def search(word):
11 | sql = 'select path from comics where script like "%{0}%"'.format(word)
12 | cursor = conn.cursor()
13 | l = cursor.execute(sql).fetchall()
14 | cursor.close()
15 | if l == []:
16 | return l
17 | return reduce(lambda x, y: x+y,l)
18 |
19 | def index():
20 | env = Environment(loader=FileSystemLoader('./', encoding='utf8'))
21 | tpl = env.get_template('search.html')
22 | word = cgi.FieldStorage().getvalue('word')
23 | results = search(word)
24 | params = {'results':results,'count':len(results)}
25 | html = tpl.render(params)
26 | print('Content-Type: text/html; charset=utf-8\n')
27 | print(html.encode('utf-8'))
28 |
29 | if __name__ == '__main__':
30 | index()
31 |
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | yuyushiki
2 | =========
3 |
4 | Yuyushiki Annotation Tool
5 |
6 | 依存関係
7 | -----------
8 | * Python 3.4 or higher ( 実は2.xでも動く )
9 | * flask ( cgiではアレかもわからん )
10 | * MongoDB + pymongo
11 |
12 | 仕様
13 | ------------
14 | 1. data/hoge ディレクトリの画像を見に行きます
15 | 2. 画像とフォームをブラウザに表示します
16 | 3. タグ付け等アノテーションをしてもらいます
17 | 4. submitされたら、データを記録し、次の画像を表示します
18 |
19 | データ形式
20 | ----------------
21 | monogoDB使う。yuyushiki.comics
22 |
23 | * 画像ファイルのパス [主キー] : path
24 | * 台詞文字列 : script
25 | * [人物タグ] : characters
26 | * 要修正 bool : reedit, デフォルトオフ
27 | * いらない画像 bool : useless, デフォルトオフ
28 |
29 | * characters : ["野々原ゆずこ", "櫟井唯", "日向縁", "相川千穂", "岡野佳", "長谷川ふみ", "松本頼子", "その他", "無し"]
30 |
31 |
32 | UI
33 | ----------------
34 | * 台詞入力, データの分別と、キャラのタグ付け作業はurlごと分離する
35 | * ↑ 独立して作業が可能に
36 | * 台詞は人間ががんばって入力する
37 | * 人物タグは補完ができるようにすると良いかも
38 | * ↑ あるいはシステム側にタグとして持たせておく
39 | * 3人全員タグ
40 | * キーボードショートカットを実装しておく
41 | * 単一ページでがんばるぞい
42 | * 進捗バーを表示する
43 | * 戻るボタンが実装された
44 |
45 | その他
46 | ----------------
47 | * アノテーションしたデータが飛ぶとつらいので逐次DBにinsert
48 | * データができたらランダム4コマシステムに応用します
49 |
50 | 無限4コマシステム
51 | ====================
52 |
53 | 永遠にゆゆ式を楽しめる楽園を実現します
54 | https://github.com/non117/yuyushiki/issues/5
55 |
56 | 仕様
57 | ---------
58 | *
59 |
60 |
61 |
--------------------------------------------------------------------------------
/templates/main.js:
--------------------------------------------------------------------------------
1 | function setVal(obj){
2 | var id = $(obj).attr("id");
3 | if($(obj).hasClass('selected')){
4 | $(obj).removeClass('selected');
5 | $("#"+id+"_input").val('');
6 | }else{
7 | $(obj).addClass('selected');
8 | $("#"+id+"_input").val(id);
9 | }
10 | };
11 |
12 | $(function(){
13 | $('input').focus(function(){
14 | $(this).parent().addClass('focus');
15 | }).blur(function(){
16 | $(this).parent().removeClass('focus');
17 | });
18 | $(document).bind('keydown', 'ctrl+return', function(){
19 | $('#submit').click();
20 | });
21 | $(document).bind('keydown', 'A', function(){
22 | $('#fumi').click();
23 | });
24 | $(document).bind('keydown', 'S', function(){
25 | $('#other').click();
26 | });
27 | $(document).bind('keydown', 'D', function(){
28 | $('#aikawa').click();
29 | });
30 | $(document).bind('keydown', 'F', function(){
31 | $('#yui').click();
32 | });
33 | $(document).bind('keydown', 'H', function(){
34 | $('#kei').click();
35 | });
36 | $(document).bind('keydown', 'J', function(){
37 | $('#yukari').click();
38 | });
39 | $(document).bind('keydown', 'K', function(){
40 | $('#yuzuko').click();
41 | });
42 | $(document).bind('keydown', 'L', function(){
43 | $('#okasan').click();
44 | });
45 | });
46 |
--------------------------------------------------------------------------------
/templates/index.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block extrahead %}
3 |
4 |
16 | {% endblock %}
17 | {% block content %}
18 |
19 |
{{ path.name }}
20 |
21 |
35 |
36 |
37 |
進捗
38 |
{{ progress }} % ({{ progress }}%)
39 |
40 | {% endblock %}
41 |
42 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014, non
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | * Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | * Neither the name of the {organization} nor the names of its
15 | contributors may be used to endorse or promote products derived from
16 | this software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
29 |
--------------------------------------------------------------------------------
/infinity/json2sqlite.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import json
3 | from sqlalchemy import create_engine, Column, String, Integer
4 | from sqlalchemy.ext.declarative import declarative_base
5 | from sqlalchemy.orm import sessionmaker
6 |
7 | engine = create_engine('sqlite:///yuyushiki.db', echo=True)
8 | Base = declarative_base()
9 |
10 | class Frame(Base):
11 | __tablename__ = 'comics'
12 |
13 | path = Column(String, primary_key=True)
14 | script = Column(String)
15 | characters = Column(String)
16 | frameno = Column(Integer)
17 |
18 | def __init__(self, script, path, characters, frameno):
19 | self.path = path
20 | self.script = script
21 | self.characters = self.list2str(characters)
22 | self.frameno = frameno
23 |
24 | def __repr__(self):
25 | return self.path
26 |
27 | @staticmethod
28 | def list2str(l):
29 | return ','.join(l)
30 |
31 | @staticmethod
32 | def str2list(s):
33 | return s.split(',')
34 |
35 | def get_frameno(filename):
36 | no = int(filename[-5])
37 | return no % 4 + 1
38 |
39 | def main():
40 | Base.metadata.create_all(engine)
41 | session = sessionmaker(bind=engine)()
42 | filepath = '../yuyushiki.json'
43 | db = json.load(open(filepath))
44 | for data in db:
45 | if data['useless'] or data['reedit']:
46 | continue
47 | no = get_frameno(data['path'])
48 | frame = Frame(data['script'], data['path'], data['characters'], no)
49 | session.add(frame)
50 | session.commit()
51 |
52 | main()
53 |
54 |
--------------------------------------------------------------------------------
/templates/tag.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block extrahead %}
3 |
31 |
32 |
33 | {% endblock %}
34 | {% block content %}
35 |
36 |
{{ path.name }}
37 |
38 |
65 |
66 |
67 |
進捗
68 |
{{ progress }} % ({{ progress }}%)
69 |
70 | {% endblock %}
71 |
72 |
--------------------------------------------------------------------------------
/templates/lib/jquery.hotkeys.js:
--------------------------------------------------------------------------------
1 | /*
2 | * jQuery Hotkeys Plugin
3 | * Copyright 2010, John Resig
4 | * Dual licensed under the MIT or GPL Version 2 licenses.
5 | *
6 | * Based upon the plugin by Tzury Bar Yochay:
7 | * http://github.com/tzuryby/hotkeys
8 | *
9 | * Original idea by:
10 | * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/
11 | */
12 |
13 | /*
14 | * One small change is: now keys are passed by object { keys: '...' }
15 | * Might be useful, when you want to pass some other data to your handler
16 | */
17 |
18 | (function(jQuery){
19 |
20 | jQuery.hotkeys = {
21 | version: "0.8",
22 |
23 | specialKeys: {
24 | 8: "backspace", 9: "tab", 10: "return", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause",
25 | 20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home",
26 | 37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del", 59: ";", 61: "=",
27 | 96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7",
28 | 104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/",
29 | 112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8",
30 | 120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 173: "-", 186: ";", 187: "=",
31 | 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 221: "]", 222: "'"
32 | },
33 |
34 | shiftNums: {
35 | "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&",
36 | "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<",
37 | ".": ">", "/": "?", "\\": "|"
38 | },
39 |
40 | // excludes: button, checkbox, file, hidden, image, password, radio, reset, search, submit, url
41 | textAcceptingInputTypes: [
42 | "text", "password", "number", "email", "url", "range", "date", "month", "week", "time", "datetime",
43 | "datetime-local", "search", "color", "tel"],
44 |
45 | options: {
46 | filterTextInputs: true
47 | }
48 | };
49 |
50 | function keyHandler( handleObj ) {
51 | if ( typeof handleObj.data === "string" ) {
52 | handleObj.data = { keys: handleObj.data };
53 | }
54 |
55 | // Only care when a possible input has been specified
56 | if ( !handleObj.data || !handleObj.data.keys || typeof handleObj.data.keys !== "string" ) {
57 | return;
58 | }
59 |
60 | var origHandler = handleObj.handler,
61 | keys = handleObj.data.keys.toLowerCase().split(" ");
62 |
63 | handleObj.handler = function( event ) {
64 | // Don't fire in text-accepting inputs that we didn't directly bind to
65 | if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) ||
66 | ( jQuery.hotkeys.options.filterTextInputs &&
67 | jQuery.inArray(event.target.type, jQuery.hotkeys.textAcceptingInputTypes) > -1 ) ) ) {
68 | return;
69 | }
70 |
71 | var special = jQuery.hotkeys.specialKeys[ event.keyCode ],
72 | character = String.fromCharCode( event.which ).toLowerCase(),
73 | modif = "", possible = {};
74 |
75 | jQuery.each([ "alt", "ctrl", "meta", "shift" ], function(index, specialKey) {
76 | if (event[specialKey + 'Key'] && special !== specialKey) {
77 | modif += specialKey + '+';
78 | }
79 | });
80 |
81 |
82 | modif = modif.replace('alt+ctrl+meta+shift', 'hyper');
83 |
84 | if ( special ) {
85 | possible[ modif + special ] = true;
86 | }
87 |
88 | if ( character ) {
89 | possible[ modif + character ] = true;
90 | possible[ modif + jQuery.hotkeys.shiftNums[ character ] ] = true;
91 |
92 | // "$" can be triggered as "Shift+4" or "Shift+$" or just "$"
93 | if ( modif === "shift+" ) {
94 | possible[ jQuery.hotkeys.shiftNums[ character ] ] = true;
95 | }
96 | }
97 |
98 | for ( var i = 0, l = keys.length; i < l; i++ ) {
99 | if ( possible[ keys[i] ] ) {
100 | return origHandler.apply( this, arguments );
101 | }
102 | }
103 | };
104 | }
105 |
106 | jQuery.each([ "keydown", "keyup", "keypress" ], function() {
107 | jQuery.event.special[ this ] = { add: keyHandler };
108 | });
109 |
110 | })( this.jQuery );
111 |
--------------------------------------------------------------------------------
/yuyushiki.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import atexit
3 | from functools import reduce
4 | from pathlib import Path
5 |
6 | from flask import Flask, render_template, send_from_directory, request, send_file
7 | from pymongo import Connection
8 |
9 | app = Flask(__name__)
10 | app.config.update(
11 | DEBUG=True,
12 | )
13 |
14 |
15 | con = Connection('localhost', 27017)
16 | collection = con.yuyushiki.comics
17 | atexit.register(con.close)
18 |
19 | root = Path('data')
20 | pages = reduce(lambda a, b:a+b, [[p for p in path.iterdir()] for path in root.iterdir()])
21 | pages.sort()
22 |
23 | def get_latest():
24 | latest = list(collection.find({}).sort('_id',-1).limit(1))
25 | if latest == []:
26 | return pages[0]
27 | else:
28 | i = pages.index(Path(latest[0]['path']))
29 | return pages[i+1]
30 |
31 | def get_tag_latest():
32 | latest = list(collection.find({'characters':[]}).limit(1))
33 | if latest == [] and collection.count() > 0:
34 | return get_latest()
35 | if latest == []:
36 | return pages[0]
37 | else:
38 | i = pages.index(Path(latest[0]['path']))
39 | return pages[i]
40 |
41 | def find_one(path):
42 | return collection.find_one({'path':path})
43 |
44 | def upsert(path, script='', characters=[], reedit=False, useless=False):
45 | d = {'path':path, 'script':script, 'characters':characters,
46 | 'reedit':reedit, 'useless':useless}
47 | collection.update({'path':path}, d, upsert=True)
48 |
49 | @app.route('/', methods=['GET', 'POST'])
50 | def index():
51 | prev = False
52 | if request.method == 'POST':
53 | data = request.form
54 | if data.get('action') == 'prev': #前の画像に戻る
55 | path = data.get('path')
56 | prev = True
57 | else:
58 | script = data.get('script', '')
59 | path = data.get('path')
60 | characters = []
61 | reedit = bool(data.get('reedit', False))
62 | useless = bool(data.get('useless', False))
63 | upsert(path, script, characters, reedit, useless)
64 |
65 | i = pages.index(Path(path))
66 | if prev:
67 | if i - 1 < 0:
68 | p = pages[i]
69 | else:
70 | p = pages[i-1]
71 | else:
72 | try:
73 | p = pages[i+1]
74 | except IndexError:
75 | return render_template('finish.html')
76 | elif request.method == 'GET':
77 | p = get_latest()
78 |
79 | data = find_one(p.as_posix())
80 | progress = round(collection.count() * 100 / len(pages), 2)
81 | return render_template('index.html', path=p, progress=progress, data=data)
82 |
83 | def skip_next(data, orig):
84 | if data is None:
85 | return orig
86 | if data['useless']:
87 | data['characters'] = ['none']
88 | collection.update({'path':data['path']}, data, upsert=True)
89 | i = pages.index(Path(data['path']))
90 | next_data = find_one(pages[i+1].as_posix())
91 | if next_data is None:
92 | return find_one(pages[i].as_posix())
93 | return skip_next(next_data, orig)
94 | else:
95 | return data
96 |
97 | def skip_prev(data, orig):
98 | if data['useless']:
99 | i = pages.index(Path(data['path']))
100 | prev_data = find_one(pages[i-1].as_posix())
101 | if prev_data is None:
102 | i = pages.index(Path(orig['path']))
103 | return find_one(pages[i+1].as_posix())
104 | return skip_prev(prev_data, orig)
105 | else:
106 | return data
107 |
108 | # TODO: complete画面, pagesの最後
109 | @app.route('/tag/', methods=['GET', 'POST'])
110 | def tag():
111 | prev = False
112 | if request.method == 'POST':
113 | data = request.form
114 | if data.get('action') == 'prev':
115 | path = data.get('path')
116 | prev = True
117 | else:
118 | path = data.get('path')
119 | characters = list(filter(bool, data.getlist('characters')))
120 | if characters == []:
121 | characters.append('none')
122 | data = find_one(path)
123 | if data:
124 | data['characters'] = characters
125 | collection.update({'path':path}, data, upsert=True)
126 | else:
127 | upsert(path, characters=characters)
128 |
129 | i = pages.index(Path(path))
130 | if prev:
131 | if i - 1 < 0:
132 | p = pages[i]
133 | else:
134 | p = pages[i-1]
135 | else:
136 | try:
137 | p = pages[i+1]
138 | except IndexError:
139 | return render_template('finish.html')
140 | elif request.method == 'GET':
141 | p = get_tag_latest()
142 |
143 | data = find_one(p.as_posix())
144 | #if prev:
145 | # data = skip_prev(data, data)
146 | #else:
147 | # data = skip_next(data, data)
148 | p = Path(data['path']) if data else p
149 | characters = {c:True for c in data['characters']} if data else []
150 | progress = round(collection.find({'characters':{'$ne':[]}}).count() * 100 / len(pages), 2)
151 | return render_template('tag.html', path=p, progress=progress, characters=characters)
152 |
153 | #@app.route('/test')
154 | def test():
155 | return render_template('finish.html')
156 |
157 | @app.route('/data/')
158 | def data(filename):
159 | return send_from_directory(root.as_posix(), filename)
160 |
161 | @app.route('/img/')
162 | def img(filename):
163 | return send_from_directory('img', filename)
164 |
165 | @app.route('/js/main.js')
166 | def mainjs():
167 | return send_file('templates/main.js')
168 |
169 | @app.route('/js/lib/')
170 | def js(filename):
171 | return send_from_directory('templates/lib', filename)
172 |
173 | if __name__ == '__main__':
174 | app.run(host='0.0.0.0')
175 |
176 |
--------------------------------------------------------------------------------