nonebot-plugin-osubot 6.23.1__py3-none-any.whl → 6.24.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of nonebot-plugin-osubot might be problematic. Click here for more details.

Files changed (81) hide show
  1. nonebot_plugin_osubot/api.py +7 -5
  2. nonebot_plugin_osubot/draw/bmap.py +19 -21
  3. nonebot_plugin_osubot/draw/bp.py +1 -1
  4. nonebot_plugin_osubot/draw/echarts.py +8 -2
  5. nonebot_plugin_osubot/draw/info.py +2 -0
  6. nonebot_plugin_osubot/draw/map.py +5 -2
  7. nonebot_plugin_osubot/draw/osu_preview.py +64 -0
  8. nonebot_plugin_osubot/draw/osu_preview_templates/css/style.css +258 -0
  9. nonebot_plugin_osubot/draw/osu_preview_templates/gif.js/README.md +109 -0
  10. nonebot_plugin_osubot/draw/osu_preview_templates/gif.js/gif.js +3 -0
  11. nonebot_plugin_osubot/draw/osu_preview_templates/gif.js/gif.js.map +1 -0
  12. nonebot_plugin_osubot/draw/osu_preview_templates/gif.js/gif.worker.js +3 -0
  13. nonebot_plugin_osubot/draw/osu_preview_templates/gif.js/gif.worker.js.map +1 -0
  14. nonebot_plugin_osubot/draw/osu_preview_templates/index.html +437 -0
  15. nonebot_plugin_osubot/draw/osu_preview_templates/js/beatmap/beatmap.js +211 -0
  16. nonebot_plugin_osubot/draw/osu_preview_templates/js/beatmap/hitobject.js +29 -0
  17. nonebot_plugin_osubot/draw/osu_preview_templates/js/beatmap/point.js +55 -0
  18. nonebot_plugin_osubot/draw/osu_preview_templates/js/beatmap/scroll.js +45 -0
  19. nonebot_plugin_osubot/draw/osu_preview_templates/js/beatmap/timingpoint.js +35 -0
  20. nonebot_plugin_osubot/draw/osu_preview_templates/js/catch/LegacyRandom.js +81 -0
  21. nonebot_plugin_osubot/draw/osu_preview_templates/js/catch/PalpableCatchHitObject.js +53 -0
  22. nonebot_plugin_osubot/draw/osu_preview_templates/js/catch/bananashower.js +33 -0
  23. nonebot_plugin_osubot/draw/osu_preview_templates/js/catch/catch.js +211 -0
  24. nonebot_plugin_osubot/draw/osu_preview_templates/js/catch/fruit.js +21 -0
  25. nonebot_plugin_osubot/draw/osu_preview_templates/js/catch/juicestream.js +176 -0
  26. nonebot_plugin_osubot/draw/osu_preview_templates/js/mania/hitnote.js +21 -0
  27. nonebot_plugin_osubot/draw/osu_preview_templates/js/mania/holdnote.js +37 -0
  28. nonebot_plugin_osubot/draw/osu_preview_templates/js/mania/mania.js +164 -0
  29. nonebot_plugin_osubot/draw/osu_preview_templates/js/preview.js +61 -0
  30. nonebot_plugin_osubot/draw/osu_preview_templates/js/standard/curve/bezier2.js +33 -0
  31. nonebot_plugin_osubot/draw/osu_preview_templates/js/standard/curve/catmullcurve.js +34 -0
  32. nonebot_plugin_osubot/draw/osu_preview_templates/js/standard/curve/centripetalcatmullrom.js +30 -0
  33. nonebot_plugin_osubot/draw/osu_preview_templates/js/standard/curve/circumstancedcircle.js +47 -0
  34. nonebot_plugin_osubot/draw/osu_preview_templates/js/standard/curve/curve.js +25 -0
  35. nonebot_plugin_osubot/draw/osu_preview_templates/js/standard/curve/curvetype.js +17 -0
  36. nonebot_plugin_osubot/draw/osu_preview_templates/js/standard/curve/equaldistancemulticurve.js +70 -0
  37. nonebot_plugin_osubot/draw/osu_preview_templates/js/standard/curve/linearbezier.js +40 -0
  38. nonebot_plugin_osubot/draw/osu_preview_templates/js/standard/hitcircle.js +85 -0
  39. nonebot_plugin_osubot/draw/osu_preview_templates/js/standard/slider.js +120 -0
  40. nonebot_plugin_osubot/draw/osu_preview_templates/js/standard/spinner.js +56 -0
  41. nonebot_plugin_osubot/draw/osu_preview_templates/js/standard/standard.js +170 -0
  42. nonebot_plugin_osubot/draw/osu_preview_templates/js/taiko/donkat.js +40 -0
  43. nonebot_plugin_osubot/draw/osu_preview_templates/js/taiko/drumroll.js +34 -0
  44. nonebot_plugin_osubot/draw/osu_preview_templates/js/taiko/shaker.js +58 -0
  45. nonebot_plugin_osubot/draw/osu_preview_templates/js/taiko/taiko.js +120 -0
  46. nonebot_plugin_osubot/draw/osu_preview_templates/js/util.js +61 -0
  47. nonebot_plugin_osubot/draw/osu_preview_templates/pic.html +115 -0
  48. nonebot_plugin_osubot/draw/score.py +4 -4
  49. nonebot_plugin_osubot/file.py +1 -12
  50. nonebot_plugin_osubot/mania/__init__.py +9 -10
  51. nonebot_plugin_osubot/matcher/bp_analyze.py +9 -9
  52. nonebot_plugin_osubot/matcher/guess.py +3 -3
  53. nonebot_plugin_osubot/matcher/map_convert.py +12 -7
  54. nonebot_plugin_osubot/matcher/preview.py +10 -3
  55. nonebot_plugin_osubot/matcher/recommend.py +7 -12
  56. nonebot_plugin_osubot/osufile/mods/AP.png +0 -0
  57. nonebot_plugin_osubot/osufile/mods/CL.png +0 -0
  58. nonebot_plugin_osubot/osufile/mods/DT.png +0 -0
  59. nonebot_plugin_osubot/osufile/mods/EZ.png +0 -0
  60. nonebot_plugin_osubot/osufile/mods/FI.png +0 -0
  61. nonebot_plugin_osubot/osufile/mods/FL.png +0 -0
  62. nonebot_plugin_osubot/osufile/mods/HD.png +0 -0
  63. nonebot_plugin_osubot/osufile/mods/HR.png +0 -0
  64. nonebot_plugin_osubot/osufile/mods/HT.png +0 -0
  65. nonebot_plugin_osubot/osufile/mods/MR.png +0 -0
  66. nonebot_plugin_osubot/osufile/mods/NC.png +0 -0
  67. nonebot_plugin_osubot/osufile/mods/NF.png +0 -0
  68. nonebot_plugin_osubot/osufile/mods/PF.png +0 -0
  69. nonebot_plugin_osubot/osufile/mods/RX.png +0 -0
  70. nonebot_plugin_osubot/osufile/mods/SD.png +0 -0
  71. nonebot_plugin_osubot/osufile/mods/SO.png +0 -0
  72. nonebot_plugin_osubot/osufile/mods/TD.png +0 -0
  73. nonebot_plugin_osubot/osufile/mods/V2.png +0 -0
  74. nonebot_plugin_osubot/pp.py +7 -0
  75. nonebot_plugin_osubot/schema/__init__.py +0 -2
  76. nonebot_plugin_osubot/schema/beatmapsets.py +42 -0
  77. nonebot_plugin_osubot/schema/score.py +1 -0
  78. {nonebot_plugin_osubot-6.23.1.dist-info → nonebot_plugin_osubot-6.24.1.dist-info}/METADATA +2 -2
  79. {nonebot_plugin_osubot-6.23.1.dist-info → nonebot_plugin_osubot-6.24.1.dist-info}/RECORD +80 -39
  80. nonebot_plugin_osubot/schema/sayo_beatmap.py +0 -59
  81. {nonebot_plugin_osubot-6.23.1.dist-info → nonebot_plugin_osubot-6.24.1.dist-info}/WHEEL +0 -0
@@ -11,19 +11,19 @@ from nonebot import get_plugin_config
11
11
  from httpx import Response
12
12
 
13
13
  from .network.manager import network_manager
14
+ from .schema.beatmapsets import BeatmapSets
14
15
  from .utils import FGM
15
16
  from .config import Config
16
17
  from .mods import get_mods
17
18
  from .network import auto_retry
18
19
  from .exceptions import NetworkError
19
20
  from .network.first_response import get_first_response
20
- from .schema import User, NewScore, SayoBeatmap, RecommendData
21
+ from .schema import User, NewScore, RecommendData
21
22
  from .schema.score import UnifiedScore, NewStatistics, UnifiedBeatmap
22
23
  from .schema.ppysb import InfoResponse, ScoresResponse, V2ScoresResponse
23
24
  from .schema.user import Level, GradeCounts, UnifiedUser, UserStatistics
24
25
 
25
26
  api = "https://osu.ppy.sh/api/v2"
26
- sayoapi = "https://api.sayobot.cn"
27
27
  cache = ExpiringDict(max_len=1, max_age_seconds=86400)
28
28
  plugin_config = get_plugin_config(Config)
29
29
 
@@ -122,6 +122,7 @@ async def fetch_score_batch(
122
122
  hp=i.beatmap.drain,
123
123
  od=i.beatmap.accuracy,
124
124
  stars=i.beatmap.difficulty_rating,
125
+ convert=i.beatmap.convert,
125
126
  ),
126
127
  beatmapset=i.beatmapset,
127
128
  )
@@ -413,9 +414,10 @@ async def get_random_bg() -> Optional[bytes]:
413
414
  return res.content
414
415
 
415
416
 
416
- async def get_sayo_map_info(sid, t=0) -> SayoBeatmap:
417
- res = await safe_async_get(f"https://api.sayobot.cn/v2/beatmapinfo?K={sid}&T={t}")
418
- return SayoBeatmap(**res.json())
417
+ async def get_beatmapsets_info(sid) -> BeatmapSets:
418
+ url = f"https://osu.ppy.sh/api/v2/beatmapsets/{sid}"
419
+ res = await make_request(url, await get_headers(), "未查询到该谱面集(Setid)信息")
420
+ return BeatmapSets(**res)
419
421
 
420
422
 
421
423
  async def get_map_bg(mapid, sid, bg_name) -> BytesIO:
@@ -4,25 +4,20 @@ from datetime import datetime, timedelta
4
4
  from PIL import ImageDraw, ImageFilter, ImageEnhance
5
5
 
6
6
  from ..file import get_projectimg
7
- from ..api import get_sayo_map_info
8
- from ..exceptions import NetworkError
7
+ from ..api import get_beatmapsets_info
9
8
  from .utils import crop_bg, stars_diff, calc_songlen
10
9
  from .static import Image, BarImg, IconLs, Torus_SemiBold_20, Torus_SemiBold_40, Torus_SemiBold_50, extra_30, Stars
11
10
 
12
11
 
13
12
  async def draw_bmap_info(mapid) -> BytesIO:
14
- sayo_info = await get_sayo_map_info(mapid)
15
- if sayo_info.status == -1:
16
- raise NetworkError("在sayobot未查询到该地图")
17
- data = sayo_info.data
18
-
13
+ data = await get_beatmapsets_info(mapid)
19
14
  coverurl = f"https://assets.ppy.sh/beatmaps/{mapid}/covers/cover@2x.jpg"
20
15
  cover = await get_projectimg(coverurl)
21
16
  # 新建
22
- if len(data.bid_data) > 20:
17
+ if len(data.beatmaps) > 20:
23
18
  im_h = 400 + 102 * 20
24
19
  else:
25
- im_h = 400 + 102 * (len(data.bid_data) - 1)
20
+ im_h = 400 + 102 * (len(data.beatmaps) - 1)
26
21
  im = Image.new("RGBA", (1200, im_h), (31, 41, 46, 255))
27
22
  draw = ImageDraw.Draw(im)
28
23
  # 背景
@@ -37,35 +32,36 @@ async def draw_bmap_info(mapid) -> BytesIO:
37
32
  # mapper
38
33
  draw.text((25, 105), f"谱面作者: {data.creator}", font=Torus_SemiBold_20, anchor="lt")
39
34
  # rank时间
40
- if data.approved_date == -1:
35
+ if not data.ranked_date:
41
36
  approved_date = "谱面状态可能非ranked"
42
37
  else:
43
- datearray = datetime.utcfromtimestamp(data.approved_date)
38
+ datearray = datetime.fromisoformat(data.ranked_date.replace("Z", ""))
44
39
  approved_date = (datearray + timedelta(hours=8)).strftime("%Y-%m-%d %H:%M:%S")
45
40
  draw.text((25, 140), f"上架时间: {approved_date}", font=Torus_SemiBold_20, anchor="lt")
46
41
  # 来源
47
- draw.text((25, 175), f"Source: {data.source}", font=Torus_SemiBold_20, anchor="lt")
42
+ if data.source:
43
+ draw.text((25, 175), f"Source: {data.source}", font=Torus_SemiBold_20, anchor="lt")
48
44
  # bpm
49
45
  draw.text((1150, 105), f"BPM: {data.bpm}", font=Torus_SemiBold_20, anchor="rt")
50
46
  # 曲长
51
- music_len = calc_songlen(data.bid_data[0].length)
47
+ music_len = calc_songlen(data.beatmaps[0].total_length)
52
48
  draw.text((1150, 140), f"length: {music_len}", font=Torus_SemiBold_20, anchor="rt")
53
49
  # Setid
54
50
  draw.text((1150, 35), f"Setid: {mapid}", font=Torus_SemiBold_20, anchor="rt")
55
- gmap = sorted(data.bid_data, key=lambda k: k.star, reverse=False)
51
+ gmap = sorted(data.beatmaps, key=lambda k: k.difficulty_rating, reverse=False)
56
52
  for num, cmap in enumerate(gmap):
57
53
  if num < 20:
58
54
  h_num = 102 * num
59
55
  # 难度
60
- draw.text((20, 320 + h_num), IconLs[cmap.mode], font=extra_30, anchor="lt")
56
+ draw.text((20, 320 + h_num), IconLs[cmap.mode_int], font=extra_30, anchor="lt")
61
57
  # 星星
62
- stars_bg = stars_diff(cmap.star, Stars)
58
+ stars_bg = stars_diff(cmap.difficulty_rating, Stars)
63
59
  stars_img = stars_bg.resize((80, 30))
64
60
  im.alpha_composite(stars_img, (60, 320 + h_num))
65
61
  # diff
66
62
  im.alpha_composite(BarImg, (10, 365 + h_num))
67
63
  gc = ["CS", "HP", "OD", "AR"]
68
- for index, i in enumerate((cmap.CS, cmap.HP, cmap.OD, cmap.AR)):
64
+ for index, i in enumerate((cmap.cs, cmap.drain, cmap.accuracy, cmap.ar)):
69
65
  diff_len = int(200 * i / 10) if i <= 10 else 200
70
66
  diff_bg = Image.new("RGBA", (diff_len, 12), (255, 255, 255, 255))
71
67
  im.alpha_composite(diff_bg, (50 + 300 * index, 365 + h_num))
@@ -90,11 +86,13 @@ async def draw_bmap_info(mapid) -> BytesIO:
90
86
  anchor="lm",
91
87
  )
92
88
  # 难度
93
- if cmap.star < 6.5:
89
+ if cmap.difficulty_rating < 6.5:
94
90
  color = (0, 0, 0, 255)
95
91
  else:
96
92
  color = (255, 217, 102, 255)
97
- draw.text((65, 335 + h_num), f"★{cmap.star:.2f}", font=Torus_SemiBold_20, anchor="lm", fill=color)
93
+ draw.text(
94
+ (65, 335 + h_num), f"★{cmap.difficulty_rating:.2f}", font=Torus_SemiBold_20, anchor="lm", fill=color
95
+ )
98
96
  # version
99
97
  draw.text(
100
98
  (150, 335 + h_num),
@@ -105,14 +103,14 @@ async def draw_bmap_info(mapid) -> BytesIO:
105
103
  # mapid
106
104
  draw.text(
107
105
  (1150, 328 + h_num),
108
- f"Mapid: {cmap.bid}",
106
+ f"Mapid: {cmap.id}",
109
107
  font=Torus_SemiBold_20,
110
108
  anchor="rm",
111
109
  )
112
110
  # maxcb
113
111
  draw.text(
114
112
  (700, 328 + h_num),
115
- f"Max Combo: {cmap.maxcombo}",
113
+ f"Max Combo: {cmap.max_combo or 0}",
116
114
  font=Torus_SemiBold_20,
117
115
  anchor="lm",
118
116
  )
@@ -48,7 +48,7 @@ async def draw_bp(
48
48
  # 判断是否开启lazer模式
49
49
  if is_lazer:
50
50
  score_info.legacy_total_score = score_info.total_score
51
- if score_info.ruleset_id == 3 and not is_lazer:
51
+ if score_info.ruleset_id == 3 and not is_lazer and source != "ppysb":
52
52
  score_info.accuracy = cal_legacy_acc(score_info.statistics)
53
53
  if not is_lazer:
54
54
  is_hidden = any(i in score_info.mods for i in (Mod(acronym="HD"), Mod(acronym="FL"), Mod(acronym="FI")))
@@ -20,8 +20,14 @@ async def draw_bpa_plot(name, pp_ls, length_ls, mod_pp_ls, mapper_pp_ls) -> byte
20
20
  pic = await template_to_pic(
21
21
  template_path,
22
22
  template_name,
23
- {"name": name, "pp_ls": pp_ls, "length_ls": length_ls, "mod_pp_ls": mod_pp_ls,
24
- "mapper_pp_ls": mapper_pp_ls, "length": len(pp_ls)},
23
+ {
24
+ "name": name,
25
+ "pp_ls": pp_ls,
26
+ "length_ls": length_ls,
27
+ "mod_pp_ls": mod_pp_ls,
28
+ "mapper_pp_ls": mapper_pp_ls,
29
+ "length": len(pp_ls),
30
+ },
25
31
  )
26
32
  return pic
27
33
 
@@ -221,7 +221,9 @@ async def draw_info(uid: Union[int, str], mode: str, day: int, source: str) -> B
221
221
  # 总命中
222
222
  op, value = info_calc(statistics.total_hits, n_count)
223
223
  t_count = f"{statistics.total_hits:,}({op}{value:,})" if value != 0 else f"{statistics.total_hits:,}"
224
+ avg_hit = 0 if statistics.play_count == 0 else statistics.total_hits // statistics.play_count
224
225
  draw.text((935, 1175), t_count, font=Torus_Regular_40, anchor="rt")
226
+ draw.text((250, 945), f"(avg.{avg_hit:,})", font=Torus_Regular_25, anchor="lt")
225
227
  # 游玩时间
226
228
  sec = timedelta(seconds=statistics.play_time)
227
229
  d_time = datetime(1, 1, 1) + sec
@@ -156,8 +156,11 @@ async def draw_map_info(mapid: int, mods: list[str], is_lazer) -> BytesIO:
156
156
  if mods:
157
157
  for mods_num, s_mods in enumerate(mods):
158
158
  mods_bg = osufile / "mods" / f"{s_mods.acronym}.png"
159
- mods_img = Image.open(mods_bg).convert("RGBA")
160
- im.alpha_composite(mods_img, (700 + 50 * mods_num, 295))
159
+ try:
160
+ mods_img = Image.open(mods_bg).convert("RGBA")
161
+ im.alpha_composite(mods_img, (700 + 50 * mods_num, 295))
162
+ except FileNotFoundError:
163
+ pass
161
164
  # mapper
162
165
  icon_url = f"https://a.ppy.sh/{mapinfo.user_id}"
163
166
  user_icon = await get_projectimg(icon_url)
@@ -0,0 +1,64 @@
1
+ import base64
2
+ from pathlib import Path
3
+
4
+ import jinja2
5
+ from nonebot_plugin_htmlrender import get_new_page
6
+
7
+ from ..file import map_path, download_osu
8
+
9
+ template_path = str(Path(__file__).parent / "osu_preview_templates")
10
+
11
+
12
+ async def draw_osu_preview(beatmap_id, beatmapset_id) -> bytes:
13
+ path = map_path / str(beatmapset_id)
14
+ if not path.exists():
15
+ path.mkdir(parents=True, exist_ok=True)
16
+ osu = path / f"{beatmap_id}.osu"
17
+ if not osu.exists():
18
+ await download_osu(beatmapset_id, beatmap_id)
19
+ with open(osu, encoding="utf-8-sig") as f:
20
+ osu_file = f.read()
21
+ template_name = "pic.html"
22
+ template_env = jinja2.Environment( # noqa: S701
23
+ loader=jinja2.FileSystemLoader(template_path),
24
+ enable_async=True,
25
+ )
26
+ template = template_env.get_template(template_name)
27
+ img_selector = "img"
28
+ base_url = Path(template_path).as_uri() + "/"
29
+ worker_script_path = Path(template_path) / "gif.js" / "gif.worker.js"
30
+
31
+ # 读取 worker 脚本内容
32
+ with open(worker_script_path, encoding="utf-8") as f:
33
+ worker_script_content = f.read()
34
+ worker_base64 = base64.b64encode(worker_script_content.encode("utf-8")).decode("utf-8")
35
+ worker_data_uri = f"data:application/javascript;base64,{worker_base64}"
36
+ async with get_new_page(2) as page:
37
+ await page.goto(f"file://{template_path}")
38
+ await page.set_content(
39
+ await template.render_async(osu_file=osu_file, base_url=base_url, worker_data_uri=worker_data_uri),
40
+ wait_until="networkidle",
41
+ )
42
+ await page.wait_for_function(
43
+ f"() => document.querySelector('{img_selector}') &&"
44
+ f" document.querySelector('{img_selector}').src.startsWith('blob:')",
45
+ timeout=60000,
46
+ )
47
+ blob_url = await page.locator(img_selector).get_attribute("src")
48
+ base64_data = await page.evaluate(
49
+ """async (url) => {
50
+ const response = await fetch(url);
51
+ const blob = await response.blob();
52
+
53
+ return new Promise((resolve) => {
54
+ const reader = new FileReader();
55
+ reader.onloadend = () => {
56
+ resolve(reader.result.split(',')[1]);
57
+ };
58
+ reader.readAsDataURL(blob);
59
+ });
60
+ }""",
61
+ blob_url,
62
+ )
63
+ gif_bytes = base64.b64decode(base64_data)
64
+ return gif_bytes
@@ -0,0 +1,258 @@
1
+ html,
2
+ body {
3
+ height: 100%;
4
+ overflow: hidden;
5
+ }
6
+
7
+ body {
8
+ margin: 0;
9
+ background: #000;
10
+ }
11
+
12
+ button {
13
+ margin: 0;
14
+ padding: 0;
15
+ border: none;
16
+ outline: 0;
17
+ background: none;
18
+ color: #fff;
19
+ cursor: pointer;
20
+ width: 40px;
21
+ height: 40px;
22
+ font: 14px sans-serif;
23
+ }
24
+
25
+ #control button::-moz-focus-inner {
26
+ padding: 0;
27
+ border: 0;
28
+ }
29
+
30
+ body>* {
31
+ position: fixed;
32
+ }
33
+
34
+ #speed,
35
+ #control,
36
+ #settingsPanel,
37
+ #title,
38
+ #mania {
39
+ background: rgba(0, 0, 0, .6);
40
+ display: none;
41
+ }
42
+
43
+ body.h #speed,
44
+ body.h #control,
45
+ body.h #settingsPanel,
46
+ body.h #title,
47
+ body.h #mania.e {
48
+ display: block;
49
+ }
50
+
51
+ #container {
52
+ top: 50%;
53
+ left: 50%;
54
+ transform: translate(-50%, -50%);
55
+ background-size: cover;
56
+ background-repeat: no-repeat;
57
+ background-position: center;
58
+ }
59
+
60
+ #play {
61
+ top: 0;
62
+ left: 0;
63
+ width: 100%;
64
+ height: 100%;
65
+ font-size: 50vmin;
66
+ text-shadow: 0 0 .05em #000;
67
+ opacity: 0;
68
+ }
69
+
70
+ #play.e {
71
+ opacity: .6;
72
+ }
73
+
74
+ #speed {
75
+ top: 50%;
76
+ right: 0;
77
+ transform: translateY(-50%);
78
+ width: 40px;
79
+ }
80
+
81
+ #speed button:not(.e) {
82
+ color: #666;
83
+ }
84
+
85
+ #mania {
86
+ top: 50%;
87
+ left: 0;
88
+ transform: translateY(-50%);
89
+ width: 40px;
90
+ }
91
+
92
+ #settingsPanel {
93
+ right: 10px;
94
+ bottom: 50px;
95
+ width: 150px;
96
+ border: 1px solid #aaa;
97
+ }
98
+
99
+
100
+ #settingsPanel button {
101
+ margin: 2px;
102
+ padding: 0;
103
+ border: none;
104
+ outline: 0;
105
+ background: none;
106
+ color: #fff;
107
+ cursor: pointer;
108
+ width: 146px;
109
+ height: 36px;
110
+ font: 14px sans-serif;
111
+ }
112
+
113
+ #openPanel {
114
+ top: 50%;
115
+ left: 30%;
116
+ transform: translateY(-50%);
117
+ width: 40%;
118
+ height: 50%;
119
+ border: 1px solid #aaa;
120
+ }
121
+
122
+ #openTitle {
123
+ color: #fff;
124
+ font: 16px sans-serif;
125
+ border-bottom: solid 1px #fff;
126
+ white-space: nowrap;
127
+ }
128
+
129
+ #openContent {
130
+ display: flex;
131
+ align-items: center;
132
+ justify-content: center;
133
+ flex-wrap: nowrap;
134
+ overflow-y: auto;
135
+ height: calc(100% - 30px);
136
+ align-content: center;
137
+ flex-direction: column;
138
+ }
139
+
140
+ #openContent button{
141
+ margin: 2px;
142
+ padding: 0;
143
+ border: 1px solid #aaa;
144
+ background: none;
145
+ color: #fff;
146
+ cursor: pointer;
147
+ width: calc(100% - 4px);
148
+ height: 36px;
149
+ font: 16px sans-serif;
150
+ display: flex;
151
+ align-items: center;
152
+ justify-content: center;
153
+ white-space: nowrap;
154
+ }
155
+
156
+ #fileInput {
157
+ display: none;
158
+ }
159
+
160
+ #folderInput {
161
+ display: none;
162
+ }
163
+
164
+ #settingsPanel .settingsbtn:hover, #settingsPanel .settingsbtn:active {
165
+ border: 1px solid #aaa;
166
+ }
167
+
168
+ #control {
169
+ bottom: 0;
170
+ left: 0;
171
+ width: 100%;
172
+ }
173
+
174
+ input {
175
+ margin: 4px;
176
+ padding: 0;
177
+ height: 32px;
178
+ box-sizing: border-box;
179
+ }
180
+
181
+ #playtime {
182
+ margin: 4px;
183
+ vertical-align: 10px;
184
+ padding-left: 10px;
185
+ box-sizing: border-box;
186
+ color: white;
187
+ font-size: 12px;
188
+ }
189
+
190
+ #progress {
191
+ width: calc(100% - 226px);
192
+ }
193
+
194
+ #volume {
195
+ width: 80px;
196
+ }
197
+
198
+ #title {
199
+ top: 0;
200
+ left: 0;
201
+ width: 100%;
202
+ white-space: nowrap;
203
+ }
204
+
205
+ img {
206
+ vertical-align: middle;
207
+ position: relative;
208
+ border: 0;
209
+ top: -1px;
210
+ padding: 0 10px;
211
+ }
212
+
213
+ a {
214
+ font: 14px/40px sans-serif;
215
+ border-bottom: 1px dashed transparent;
216
+ color: #fff;
217
+ text-decoration: none;
218
+ }
219
+
220
+ a:hover {
221
+ border-bottom-color: #ddd;
222
+ }
223
+
224
+ #share {
225
+ position: absolute;
226
+ top: 0;
227
+ right: 0;
228
+ }
229
+
230
+ #sharePrompt {
231
+ top: 50%;
232
+ left: 50%;
233
+ transform: translate(-50%, -50%);
234
+ background: rgba(255, 255, 255, .6);
235
+ display: none;
236
+ }
237
+
238
+ #sharePrompt button {
239
+ float: right;
240
+ background: #49f;
241
+ }
242
+
243
+ #sharePrompt input {
244
+ width: 100%;
245
+ margin: 0;
246
+ }
247
+
248
+ #fullscreen {
249
+ position: absolute;
250
+ top: 0;
251
+ right: 0;
252
+ }
253
+
254
+ #settings {
255
+ position: absolute;
256
+ top: 0;
257
+ right: 40;
258
+ }
@@ -0,0 +1,109 @@
1
+
2
+ # gif.js
3
+
4
+ JavaScript GIF encoder that runs in your browser.
5
+
6
+ Uses typed arrays and web workers to render each frame in the background, it's really fast!
7
+
8
+ **Demo** - http://jnordberg.github.io/gif.js/
9
+
10
+ Works in browsers supporting: [Web Workers](http://www.w3.org/TR/workers/), [File API](http://www.w3.org/TR/FileAPI/) and [Typed Arrays](https://www.khronos.org/registry/typedarray/specs/latest/)
11
+
12
+
13
+ ## Usage
14
+
15
+ Include `gif.js` found in `dist/` in your page. Also make sure to have `gif.worker.js` in the same location.
16
+
17
+ ```javascript
18
+ var gif = new GIF({
19
+ workers: 2,
20
+ quality: 10
21
+ });
22
+
23
+ // add an image element
24
+ gif.addFrame(imageElement);
25
+
26
+ // or a canvas element
27
+ gif.addFrame(canvasElement, {delay: 200});
28
+
29
+ // or copy the pixels from a canvas context
30
+ gif.addFrame(ctx, {copy: true});
31
+
32
+ gif.on('finished', function(blob) {
33
+ window.open(URL.createObjectURL(blob));
34
+ });
35
+
36
+ gif.render();
37
+
38
+ ```
39
+
40
+ ## Options
41
+
42
+ Options can be passed to the constructor or using the `setOptions` method.
43
+
44
+ | Name | Default | Description |
45
+ | -------------|-----------------|----------------------------------------------------|
46
+ | repeat | `0` | repeat count, `-1` = no repeat, `0` = forever |
47
+ | quality | `10` | pixel sample interval, lower is better |
48
+ | workers | `2` | number of web workers to spawn |
49
+ | workerScript | `gif.worker.js` | url to load worker script from |
50
+ | background | `#fff` | background color where source image is transparent |
51
+ | width | `null` | output image width |
52
+ | height | `null` | output image height |
53
+ | transparent | `null` | transparent hex color, `0x00FF00` = green |
54
+ | dither | `false` | dithering method, e.g. `FloydSteinberg-serpentine` |
55
+ | debug | `false` | whether to print debug information to console |
56
+
57
+ If width or height is `null` image size will be deteremined by first frame added.
58
+
59
+ Available dithering methods are:
60
+
61
+ * `FloydSteinberg`
62
+ * `FalseFloydSteinberg`
63
+ * `Stucki`
64
+ * `Atkinson`
65
+
66
+ You can add `-serpentine` to use serpentine scanning, e.g. `Stucki-serpentine`.
67
+
68
+ ### addFrame options
69
+
70
+ | Name | Default | Description |
71
+ | -------------|-----------------|----------------------------------------------------|
72
+ | delay | `500` | frame delay |
73
+ | copy | `false` | copy the pixel data |
74
+
75
+
76
+ ## Acknowledgements
77
+
78
+ gif.js is based on:
79
+
80
+ * [Kevin Weiner's Animated gif encoder classes](http://www.fmsware.com/stuff/gif.html)
81
+ * [Neural-Net color quantization algorithm by Anthony Dekker](http://members.ozemail.com.au/~dekker/NEUQUANT.HTML)
82
+ * [Thibault Imbert's as3gif](https://code.google.com/p/as3gif/)
83
+
84
+ Dithering code contributed by @PAEz and @panrafal
85
+
86
+
87
+ ## License
88
+
89
+ The MIT License (MIT)
90
+
91
+ Copyright (c) 2013 Johan Nordberg
92
+
93
+ Permission is hereby granted, free of charge, to any person obtaining a copy
94
+ of this software and associated documentation files (the "Software"), to deal
95
+ in the Software without restriction, including without limitation the rights
96
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
97
+ copies of the Software, and to permit persons to whom the Software is
98
+ furnished to do so, subject to the following conditions:
99
+
100
+ The above copyright notice and this permission notice shall be included in
101
+ all copies or substantial portions of the Software.
102
+
103
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
104
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
105
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
106
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
107
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
108
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
109
+ THE SOFTWARE.
@@ -0,0 +1,3 @@
1
+ // gif.js 0.2.0 - https://github.com/jnordberg/gif.js
2
+ (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.GIF=f()}})(function(){var define,module,exports;return function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s}({1:[function(require,module,exports){function EventEmitter(){this._events=this._events||{};this._maxListeners=this._maxListeners||undefined}module.exports=EventEmitter;EventEmitter.EventEmitter=EventEmitter;EventEmitter.prototype._events=undefined;EventEmitter.prototype._maxListeners=undefined;EventEmitter.defaultMaxListeners=10;EventEmitter.prototype.setMaxListeners=function(n){if(!isNumber(n)||n<0||isNaN(n))throw TypeError("n must be a positive number");this._maxListeners=n;return this};EventEmitter.prototype.emit=function(type){var er,handler,len,args,i,listeners;if(!this._events)this._events={};if(type==="error"){if(!this._events.error||isObject(this._events.error)&&!this._events.error.length){er=arguments[1];if(er instanceof Error){throw er}else{var err=new Error('Uncaught, unspecified "error" event. ('+er+")");err.context=er;throw err}}}handler=this._events[type];if(isUndefined(handler))return false;if(isFunction(handler)){switch(arguments.length){case 1:handler.call(this);break;case 2:handler.call(this,arguments[1]);break;case 3:handler.call(this,arguments[1],arguments[2]);break;default:args=Array.prototype.slice.call(arguments,1);handler.apply(this,args)}}else if(isObject(handler)){args=Array.prototype.slice.call(arguments,1);listeners=handler.slice();len=listeners.length;for(i=0;i<len;i++)listeners[i].apply(this,args)}return true};EventEmitter.prototype.addListener=function(type,listener){var m;if(!isFunction(listener))throw TypeError("listener must be a function");if(!this._events)this._events={};if(this._events.newListener)this.emit("newListener",type,isFunction(listener.listener)?listener.listener:listener);if(!this._events[type])this._events[type]=listener;else if(isObject(this._events[type]))this._events[type].push(listener);else this._events[type]=[this._events[type],listener];if(isObject(this._events[type])&&!this._events[type].warned){if(!isUndefined(this._maxListeners)){m=this._maxListeners}else{m=EventEmitter.defaultMaxListeners}if(m&&m>0&&this._events[type].length>m){this._events[type].warned=true;console.error("(node) warning: possible EventEmitter memory "+"leak detected. %d listeners added. "+"Use emitter.setMaxListeners() to increase limit.",this._events[type].length);if(typeof console.trace==="function"){console.trace()}}}return this};EventEmitter.prototype.on=EventEmitter.prototype.addListener;EventEmitter.prototype.once=function(type,listener){if(!isFunction(listener))throw TypeError("listener must be a function");var fired=false;function g(){this.removeListener(type,g);if(!fired){fired=true;listener.apply(this,arguments)}}g.listener=listener;this.on(type,g);return this};EventEmitter.prototype.removeListener=function(type,listener){var list,position,length,i;if(!isFunction(listener))throw TypeError("listener must be a function");if(!this._events||!this._events[type])return this;list=this._events[type];length=list.length;position=-1;if(list===listener||isFunction(list.listener)&&list.listener===listener){delete this._events[type];if(this._events.removeListener)this.emit("removeListener",type,listener)}else if(isObject(list)){for(i=length;i-- >0;){if(list[i]===listener||list[i].listener&&list[i].listener===listener){position=i;break}}if(position<0)return this;if(list.length===1){list.length=0;delete this._events[type]}else{list.splice(position,1)}if(this._events.removeListener)this.emit("removeListener",type,listener)}return this};EventEmitter.prototype.removeAllListeners=function(type){var key,listeners;if(!this._events)return this;if(!this._events.removeListener){if(arguments.length===0)this._events={};else if(this._events[type])delete this._events[type];return this}if(arguments.length===0){for(key in this._events){if(key==="removeListener")continue;this.removeAllListeners(key)}this.removeAllListeners("removeListener");this._events={};return this}listeners=this._events[type];if(isFunction(listeners)){this.removeListener(type,listeners)}else if(listeners){while(listeners.length)this.removeListener(type,listeners[listeners.length-1])}delete this._events[type];return this};EventEmitter.prototype.listeners=function(type){var ret;if(!this._events||!this._events[type])ret=[];else if(isFunction(this._events[type]))ret=[this._events[type]];else ret=this._events[type].slice();return ret};EventEmitter.prototype.listenerCount=function(type){if(this._events){var evlistener=this._events[type];if(isFunction(evlistener))return 1;else if(evlistener)return evlistener.length}return 0};EventEmitter.listenerCount=function(emitter,type){return emitter.listenerCount(type)};function isFunction(arg){return typeof arg==="function"}function isNumber(arg){return typeof arg==="number"}function isObject(arg){return typeof arg==="object"&&arg!==null}function isUndefined(arg){return arg===void 0}},{}],2:[function(require,module,exports){var UA,browser,mode,platform,ua;ua=navigator.userAgent.toLowerCase();platform=navigator.platform.toLowerCase();UA=ua.match(/(opera|ie|firefox|chrome|version)[\s\/:]([\w\d\.]+)?.*?(safari|version[\s\/:]([\w\d\.]+)|$)/)||[null,"unknown",0];mode=UA[1]==="ie"&&document.documentMode;browser={name:UA[1]==="version"?UA[3]:UA[1],version:mode||parseFloat(UA[1]==="opera"&&UA[4]?UA[4]:UA[2]),platform:{name:ua.match(/ip(?:ad|od|hone)/)?"ios":(ua.match(/(?:webos|android)/)||platform.match(/mac|win|linux/)||["other"])[0]}};browser[browser.name]=true;browser[browser.name+parseInt(browser.version,10)]=true;browser.platform[browser.platform.name]=true;module.exports=browser},{}],3:[function(require,module,exports){var EventEmitter,GIF,browser,extend=function(child,parent){for(var key in parent){if(hasProp.call(parent,key))child[key]=parent[key]}function ctor(){this.constructor=child}ctor.prototype=parent.prototype;child.prototype=new ctor;child.__super__=parent.prototype;return child},hasProp={}.hasOwnProperty,indexOf=[].indexOf||function(item){for(var i=0,l=this.length;i<l;i++){if(i in this&&this[i]===item)return i}return-1},slice=[].slice;EventEmitter=require("events").EventEmitter;browser=require("./browser.coffee");GIF=function(superClass){var defaults,frameDefaults;extend(GIF,superClass);defaults={workerScript:"gif.worker.js",workers:2,repeat:0,background:"#fff",quality:10,width:null,height:null,transparent:null,debug:false,dither:false};frameDefaults={delay:500,copy:false};function GIF(options){var base,key,value;this.running=false;this.options={};this.frames=[];this.freeWorkers=[];this.activeWorkers=[];this.setOptions(options);for(key in defaults){value=defaults[key];if((base=this.options)[key]==null){base[key]=value}}}GIF.prototype.setOption=function(key,value){this.options[key]=value;if(this._canvas!=null&&(key==="width"||key==="height")){return this._canvas[key]=value}};GIF.prototype.setOptions=function(options){var key,results,value;results=[];for(key in options){if(!hasProp.call(options,key))continue;value=options[key];results.push(this.setOption(key,value))}return results};GIF.prototype.addFrame=function(image,options){var frame,key;if(options==null){options={}}frame={};frame.transparent=this.options.transparent;for(key in frameDefaults){frame[key]=options[key]||frameDefaults[key]}if(this.options.width==null){this.setOption("width",image.width)}if(this.options.height==null){this.setOption("height",image.height)}if(typeof ImageData!=="undefined"&&ImageData!==null&&image instanceof ImageData){frame.data=image.data}else if(typeof CanvasRenderingContext2D!=="undefined"&&CanvasRenderingContext2D!==null&&image instanceof CanvasRenderingContext2D||typeof WebGLRenderingContext!=="undefined"&&WebGLRenderingContext!==null&&image instanceof WebGLRenderingContext){if(options.copy){frame.data=this.getContextData(image)}else{frame.context=image}}else if(image.childNodes!=null){if(options.copy){frame.data=this.getImageData(image)}else{frame.image=image}}else{throw new Error("Invalid image")}return this.frames.push(frame)};GIF.prototype.render=function(){var i,j,numWorkers,ref;if(this.running){throw new Error("Already running")}if(this.options.width==null||this.options.height==null){throw new Error("Width and height must be set prior to rendering")}this.running=true;this.nextFrame=0;this.finishedFrames=0;this.imageParts=function(){var j,ref,results;results=[];for(i=j=0,ref=this.frames.length;0<=ref?j<ref:j>ref;i=0<=ref?++j:--j){results.push(null)}return results}.call(this);numWorkers=this.spawnWorkers();if(this.options.globalPalette===true){this.renderNextFrame()}else{for(i=j=0,ref=numWorkers;0<=ref?j<ref:j>ref;i=0<=ref?++j:--j){this.renderNextFrame()}}this.emit("start");return this.emit("progress",0)};GIF.prototype.abort=function(){var worker;while(true){worker=this.activeWorkers.shift();if(worker==null){break}this.log("killing active worker");worker.terminate()}this.running=false;return this.emit("abort")};GIF.prototype.spawnWorkers=function(){var j,numWorkers,ref,results;numWorkers=Math.min(this.options.workers,this.frames.length);(function(){results=[];for(var j=ref=this.freeWorkers.length;ref<=numWorkers?j<numWorkers:j>numWorkers;ref<=numWorkers?j++:j--){results.push(j)}return results}).apply(this).forEach(function(_this){return function(i){var worker;_this.log("spawning worker "+i);worker=new Worker(_this.options.workerScript);worker.onmessage=function(event){_this.activeWorkers.splice(_this.activeWorkers.indexOf(worker),1);_this.freeWorkers.push(worker);return _this.frameFinished(event.data)};return _this.freeWorkers.push(worker)}}(this));return numWorkers};GIF.prototype.frameFinished=function(frame){var i,j,ref;this.log("frame "+frame.index+" finished - "+this.activeWorkers.length+" active");this.finishedFrames++;this.emit("progress",this.finishedFrames/this.frames.length);this.imageParts[frame.index]=frame;if(this.options.globalPalette===true){this.options.globalPalette=frame.globalPalette;this.log("global palette analyzed");if(this.frames.length>2){for(i=j=1,ref=this.freeWorkers.length;1<=ref?j<ref:j>ref;i=1<=ref?++j:--j){this.renderNextFrame()}}}if(indexOf.call(this.imageParts,null)>=0){return this.renderNextFrame()}else{return this.finishRendering()}};GIF.prototype.finishRendering=function(){var data,frame,i,image,j,k,l,len,len1,len2,len3,offset,page,ref,ref1,ref2;len=0;ref=this.imageParts;for(j=0,len1=ref.length;j<len1;j++){frame=ref[j];len+=(frame.data.length-1)*frame.pageSize+frame.cursor}len+=frame.pageSize-frame.cursor;this.log("rendering finished - filesize "+Math.round(len/1e3)+"kb");data=new Uint8Array(len);offset=0;ref1=this.imageParts;for(k=0,len2=ref1.length;k<len2;k++){frame=ref1[k];ref2=frame.data;for(i=l=0,len3=ref2.length;l<len3;i=++l){page=ref2[i];data.set(page,offset);if(i===frame.data.length-1){offset+=frame.cursor}else{offset+=frame.pageSize}}}image=new Blob([data],{type:"image/gif"});return this.emit("finished",image,data)};GIF.prototype.renderNextFrame=function(){var frame,task,worker;if(this.freeWorkers.length===0){throw new Error("No free workers")}if(this.nextFrame>=this.frames.length){return}frame=this.frames[this.nextFrame++];worker=this.freeWorkers.shift();task=this.getTask(frame);this.log("starting frame "+(task.index+1)+" of "+this.frames.length);this.activeWorkers.push(worker);return worker.postMessage(task)};GIF.prototype.getContextData=function(ctx){return ctx.getImageData(0,0,this.options.width,this.options.height).data};GIF.prototype.getImageData=function(image){var ctx;if(this._canvas==null){this._canvas=document.createElement("canvas");this._canvas.width=this.options.width;this._canvas.height=this.options.height}ctx=this._canvas.getContext("2d");ctx.setFill=this.options.background;ctx.fillRect(0,0,this.options.width,this.options.height);ctx.drawImage(image,0,0);return this.getContextData(ctx)};GIF.prototype.getTask=function(frame){var index,task;index=this.frames.indexOf(frame);task={index:index,last:index===this.frames.length-1,delay:frame.delay,transparent:frame.transparent,width:this.options.width,height:this.options.height,quality:this.options.quality,dither:this.options.dither,globalPalette:this.options.globalPalette,repeat:this.options.repeat,canTransfer:browser.name==="chrome"};if(frame.data!=null){task.data=frame.data}else if(frame.context!=null){task.data=this.getContextData(frame.context)}else if(frame.image!=null){task.data=this.getImageData(frame.image)}else{throw new Error("Invalid frame")}return task};GIF.prototype.log=function(){var args;args=1<=arguments.length?slice.call(arguments,0):[];if(!this.options.debug){return}return console.log.apply(console,args)};return GIF}(EventEmitter);module.exports=GIF},{"./browser.coffee":2,events:1}]},{},[3])(3)});
3
+ //# sourceMappingURL=gif.js.map