ホーム 記事一覧 ブック DH週間トピックス 検索 このサイトについて
English
Next.js 15 で output: 'export' 使用時の sitemap.ts 実装方法

Next.js 15 で output: 'export' 使用時の sitemap.ts 実装方法

この記事は人間が実装を確認したのち、AIが記事を執筆しました。 背景 Next.js 15で静的サイト生成(output: 'export')を使用する際、sitemap.tsの実装でエラーが発生する場合があります。 Error: export const dynamic = "force-static"/export const revalidate not configured on route "/sitemap.xml" with "output: export". 解決方法 この問題は、sitemap.tsに以下の2つのエクスポートを追加することで解決できます: // src/app/sitemap.ts import { MetadataRoute } from 'next'; export const dynamic = 'force-static'; export const revalidate = false; export default function sitemap(): MetadataRoute.Sitemap { // sitemap生成ロジック } 実装例 import { MetadataRoute } from 'next'; import { routing } from '@/i18n/routing'; export const dynamic = 'force-static'; export const revalidate = false; export default function sitemap(): MetadataRoute.Sitemap { const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000'; const staticPages = [ '', // Home page '/about', '/example', ]; const sitemapEntries: MetadataRoute.Sitemap = routing.locales.flatMap((locale) => staticPages.map((page) => ({ url: `${baseUrl}/${locale}${page}`, lastModified: new Date(), changeFrequency: page === '' ? 'daily' : 'weekly' as const, priority: page === '' ? 1 : 0.8, })) ); return sitemapEntries; } 動作確認 この実装により、npm run build実行時に/out/sitemap.xmlが正常に生成されます。 注意点 dynamic = 'force-static'のみでは不十分で、revalidate = falseも必要です 環境変数やインポートしたモジュールの使用も可能です ビルド時に静的に解決される値であれば、動的な値も使用できます 代替案 もし上記の方法で解決しない場合は、以下の代替案があります: ...

IIIF認証API 2.0の動作確認

IIIF認証API 2.0の動作確認

概要 以下のIIIF認証API 2.0の動作確認を行う機会がありましたので、備忘録です。 https://iiif.io/api/auth/2.0/ 以下のようなデモサイトを作成しました。 https://iiif-auth-nextjs.vercel.app/ja リポジトリは以下です。 https://github.com/nakamura196/iiif-auth-nextjs 以下、AIによる説明です。なお、Miradorではうまく動作させることができなかったため、今後の課題です。 概要 本記事では、IIIF Authentication API 2.0 の認証フローを、実際のHTTPリクエスト/レスポンスのレベルで詳細に解説します。各ステップでどのようなリクエストが送信され、どのようなレスポンスが返されるのかを追跡していきます。 アーキテクチャ概要 ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ Client │────▶│ IIIF Server │────▶│Auth Service │ │ (Browser) │◀────│ │◀────│ │ └─────────────┘ └─────────────┘ └─────────────┘ 認証フローの詳細 Step 1: 初回の画像情報リクエスト(未認証) リクエスト: GET /api/iiif/image/sample/info.json HTTP/1.1 Host: localhost:3001 Accept: application/json 処理フロー(サーバー側): // app/api/iiif/image/[id]/info.json/route.ts export async function GET(request: NextRequest) { // 1. Authorizationヘッダーを確認 const authHeader = request.headers.get('authorization'); let token = authHeader?.replace('Bearer ', ''); // 2. クエリパラメータもチェック(フォールバック) if (!token) { token = request.nextUrl.searchParams.get('token') || ''; } // 3. トークンの検証 const isValid = token ? await verifyToken(token) : null; // 4. 未認証の場合は401を返す if (!isValid) { return NextResponse.json({ error: 'Authentication required', service: [{ "@context": "http://iiif.io/api/auth/2/context.json", "id": `${request.nextUrl.origin}/api/iiif/probe`, "type": "AuthProbeService2" }] }, { status: 401 }); } } レスポンス: ...

GitHub File History Analyzerの紹介:ファイル編集履歴をAIで分析するツール

GitHub File History Analyzerの紹介:ファイル編集履歴をAIで分析するツール

本記事はAIが作成しました。 はじめに GitHubリポジトリで管理されているファイルの編集履歴を分析したいと思ったことはありませんか?特に長期間にわたって更新されているファイルの変更パターンや、プロジェクトの進化の過程を理解したい場合があります。 GitHub File History Analyzerは、このようなニーズに応えるために開発したコマンドラインツールです。 ツールの概要 このツールは以下の機能を提供します: GitHubのAPIを使用して特定ファイルのコミット履歴を取得 変更内容の統計的な分析(追加・削除行数、変更タイプの分類など) OpenRouter経由でAI(Gemini 2.5 Proなど)による編集パターンの分析 分析結果のMarkdown/JSON形式での出力 開発の背景 デジタルアーカイブプロジェクトで、XMLファイルの長期的な編集作業を追跡する必要がありました。単純なgit logでは得られない、より深い洞察(編集の傾向、作業の質、進捗状況など)を得たいという要求から、このツールの開発に至りました。 技術的な実装 使用技術 言語 : Python 3.8+ 主要ライブラリ : PyGithub(GitHub API wrapper) requests(HTTP通信) python-dotenv(環境変数管理) アーキテクチャ ツールは主に2つのコンポーネントで構成されています: GitHubFileHistoryAnalyzer : GitHub APIを使用してファイル履歴を取得・分析 OpenRouterClient : AI分析のためのクライアント # 基本的な使用例 analyzer = GitHubFileHistoryAnalyzer(github_token) commits = analyzer.get_file_history("owner/repo", "path/to/file.xml") analysis = analyzer.analyze_patches(commits) prompt = analyzer.generate_ai_prompt(commits, analysis) 実際の使用例 基本的なコマンド # ファイル履歴の取得と表示 python main.py --repo owner/repo --file path/to/file.py # AI分析の実行 python main.py --repo owner/repo --file path/to/file.py --analyze # 結果をMarkdown形式で保存 python main.py --analyze --ai-output analysis.md 分析結果の例 ツールは以下のような情報を提供します: ...

Odeuropaに関連するツールを試す

Odeuropaに関連するツールを試す

概要 Odeuropaに関連するツールを試す機会がありましたので、備忘録です。 Odeuropaとは 以下のページに説明があります。 https://odeuropa.eu/ 以下、機械翻訳の結果です。 Odeuropa(オドゥーロパ)は、ヨーロッパの「匂いの文化遺産」を研究するEU資金による革新的なプロジェクトです。 プロジェクトの目的: 1600年から1920年までのヨーロッパの歴史において、匂いが文化にどのような役割を果たしてきたかを調査・記録することです。最新のAI技術を使って、約4万3千点の画像と16万7千点の歴史的テキスト(英語、イタリア語、フランス語、オランダ語、ドイツ語、スロベニア語)から匂いに関する情報を抽出しています。 主な成果物: 匂い探索エンジン - 300年以上のヨーロッパの匂いの歴史を検索できるユニークなウェブサイト 匂いの歴史・遺産百科事典 - 専門家が執筆した匂いに関する文化的現象のオンライン参考書 嗅覚ストーリーテリング・ツールキット - 博物館や文化遺産機関が匂いを展示に活用するための実践ガイド 遺産匂いライブラリー - 歴史的に重要な匂いを調香師と協力して再現し、保存するコレクション このプロジェクトは、従来の視覚や聴覚中心の文化遺産の理解に、「嗅覚」という新しい次元を加えることで、より豊かで多感覚的な歴史体験を可能にしています。 ツール 以下のページで、関連する幾つかのツールが紹介されています。 https://odeuropa.eu/nosebooks/ Live Image Processing Demo 国立国会図書館の「近代日本人の肖像」で公開されている以下の画像を利用しました。 https://www.ndl.go.jp/portrait/datas/224 以下にアクセスし、画像のURLを指定します。 https://huggingface.co/spaces/mathiaszinnen/odeuropa-demo https://www.ndl.go.jp/portrait/img/portrait/0224_6.jpg 「Confidence Threshold」を0.2に設定した結果は以下です。手に持っているタバコを認識できているようでした。 Smells Extraction テキストから匂いを抽出するツールのようです。 https://smell-extractor.tools.eurecom.fr/ 匂いに関する記述を含む英語のサンプルテキストをAIに作成してもらいました。 As I walked past the bakery, the warm aroma of freshly baked bread and cinnamon rolls wafted through the air, making my mouth water instantly. Later that afternoon, when I stepped outside after the summer storm, the petrichor filled my nostrils - that distinctive earthy scent of rain on dry soil mixing with the sweet fragrance of wet grass. It reminded me of my visit to the old library last week, where the musty smell of aged paper and leather-bound books had greeted me as I entered, accompanied by hints of vanilla and almond from the decomposing lignin in the yellowing pages. This morning started differently though. The rich, nutty aroma of freshly ground coffee beans permeated the kitchen, its bitter-sweet scent promising the perfect start to my day. It was such a contrast to yesterday evening when I had wandered through the garden, where the heady perfume of jasmine blossoms hung heavy in the air, intermingling with the sharp, green smell of freshly cut grass and the subtle sweetness of roses. Nothing could have prepared me for today's trip to the seafood market, however, where the pungent odor of fish and brine assaulted my senses as I navigated through the crowded stalls, the salty tang of the ocean mixing with the metallic scent of ice and seafood. これを登録した結果が以下です。匂いに関する「Location」「Quality」「Smell Word」などが検出されていました。 ...

AllegroGraphを使ってみる

AllegroGraphを使ってみる

概要 AllegroGraphを使ってみる機会がありましたので、備忘録です。 https://allegrograph.com/ 使い方 いくつかのセットアップ方法があるようですが、以下のクラウド版を利用してみます。 https://allegrograph.cloud/ セットアップ後、以下のように表示されます。 ログイン後、複数のRepositoriesが表示されます。 匿名アクセスを可能にする 例えば、リポジトリ「actors」のSPARQLエンドポイントは以下です。 https://ag1edt2www58hzzy.allegrograph.cloud/repositories/actors/sparql デフォルトでは、Basic認証がかかります。 そこで、anonymousユーザを作成します。ユーザ名を「anonymous」にすることで、パスワードの入力をスキップできました。 公式の説明は以下に記載があります。 https://franz.com/agraph/support/documentation/managing-users.html#anonymous-access そして、「actors」リポジトリへのREADを許可します。 この設定により、指定したリポジトリへ認証なしでアクセスできるようになります。以下は、yasguiでアクセスした例です。 https://yasgui.org/#query=PREFIX+rdf%3A+<http%3A%2F%2Fwww.w3.org%2F1999%2F02%2F22-rdf-syntax-ns%23> PREFIX+rdfs%3A+<http%3A%2F%2Fwww.w3.org%2F2000%2F01%2Frdf-schema%23> SELECT+*+WHERE+{ ++%3Fsub+%3Fpred+%3Fobj+. }+ LIMIT+10&contentTypeConstruct=text%2Fturtle&contentTypeSelect=application%2Fsparql-results%2Bjson&endpoint=https%3A%2F%2Fag1edt2www58hzzy.allegrograph.cloud%2Frepositories%2Factors%2Fsparql&requestMethod=POST&tabTitle=Query&headers={}&outputFormat=table リポジトリを追加してみる 「my_first_repository」というリポジトリを追加しました。RDFファイルを登録してみます。 校異源氏物語テキストDBで使用している以下のRDFファイルを登録してみます。 https://github.com/kouigenjimonogatari/kouigenjimonogatari.github.io/blob/master/files/rdf/item_sets.rdf 登録時に、Graph URIを任意項目として登録することができます。入力例にしたがって、以下のように入力しました。 エクスポートする 「Repository Control」の「Archiving」からエクスポートできます。 「N-Quads」を選択して、エクスポートした結果が以下です。トリプルに加えて、先ほど登録したGraph URIが出力されていることが確認できます。 <https://w3id.org/kouigenjimonogatari/api/item_sets/31.json> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://jpsearch.go.jp/term/type/作品> <http://example.org> . <https://w3id.org/kouigenjimonogatari/api/item_sets/31.json> <http://purl.org/dc/terms/relation> <https://w3id.org/kouigenjimonogatari/tei/31.xml> <http://example.org> . <https://w3id.org/kouigenjimonogatari/api/item_sets/31.json> <https://w3id.org/kouigenjimonogatari/api/property/vol> "31"^^<http://www.w3.org/2001/XMLSchema#integer> <http://example.org> . <https://w3id.org/kouigenjimonogatari/api/item_sets/31.json> <http://www.w3.org/2000/01/rdf-schema#label> "まきはしら" <http://example.org> . ... SHACL Shapesを管理する Generate SHACL Shapesボタンを押すと、SHACLファイルが作成されます。 以下のようなJSONが作成されました。 { "@context": { "ns2": "https://jpsearch.go.jp/term/type/", "ns1": "http://www.w3.org/ns/shacl#", "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#" }, "@id": "ns2:WorkShape", "ns1:targetClass": "https://jpsearch.go.jp/term/type/Work", "ns1:property": [ { "ns1:nodeKind": "sh:IRI", "ns1:path": "rdf:type", "ns1:minCount": 1, "ns1:maxCount": 1 }, { "ns1:nodeKind": "sh:IRI", "ns1:path": "dcterms:relation", "ns1:minCount": 1, "ns1:maxCount": 1 }, { "ns1:nodeKind": "sh:IRI", "ns1:path": "rdfs:seeAlso", "ns1:minCount": 1, "ns1:maxCount": 1 }, { "ns1:nodeKind": "sh:IRI", "ns1:path": "https://w3id.org/kouigenjimonogatari/api/property/vol", "ns1:minCount": 1, "ns1:maxCount": 1 }, { "ns1:nodeKind": "sh:IRI", "ns1:path": "dcterms:rights", "ns1:minCount": 1, "ns1:maxCount": 1 }, { "ns1:nodeKind": "sh:literal", "ns1:datatype": "xs:string", "ns1:path": "rdfs:label", "ns1:minCount": 1, "ns1:maxCount": 1 } ], "@type": "ns1:NodeShape" } Queryを投げてみると、SHACLに関するトリプルも登録されていました。 ...

DHConvalidatorにおける'ref'に関する不具合への対応

DHConvalidatorにおける'ref'に関する不具合への対応

本記事は、一部AIが執筆しました。 概要 DHConvalidatorは、デジタル人文学(DH)会議の抄録を一貫したTEI(Text Encoding Initiative)テキストベースに変換するためのツールです。 https://github.com/ADHO/dhconvalidator このツールの利用において、Microsoft Word形式(DOCX)からTEI XML形式への変換処理中に以下のようなエラーが発生するケースがありました: ERROR: nu.xom.ParsingException: cvc-complex-type.2.4.a: Invalid content was found starting with element 'ref' この原因と対処方法について共有します。 原因の特定 調査の結果、問題の原因はWord文書内に埋め込まれた INCLUDEPICTUREフィールドコード であることが判明しました。 具体的には、Googleドキュメントから画像をコピー&ペーストした際に、以下のようなフィールドコードが文書内に残存していました: INCLUDEPICTURE "https://lh7-rt.googleusercontent.com/docsz/..." \* MERGEFORMATINET これらの外部画像参照リンクがTEI変換プロセスで適切に処理されず、XML検証エラーを引き起こしていました。 解決方法 この問題を解決するため、DOCXファイル内の問題のあるフィールドコードを自動的に除去するPythonスクリプトを開発しました。 スクリプトの特徴 安全な処理 : 画像コンテンツ自体は保持し、フィールドコード部分のみを削除 ZIP形式対応 : DOCXファイルの内部構造(ZIP + XML)を適切に処理 名前空間対応 : Word文書のXML名前空間を考慮した正確な要素検索 主要な処理ロジック DOCXファイルを一時ディレクトリに展開 word/document.xml内のフィールドコード構造を解析 INCLUDEPICTUREを含むフィールドを特定 フィールド制御要素(begin/separate/end)のみを削除し、画像要素は保持 修正されたXMLで新しいDOCXファイルを生成 実装のポイント フィールドコード判定 def is_includepicture_field(field_runs, ns): for run in field_runs: instr_text = run.find('.//w:instrText', ns) if instr_text is not None and instr_text.text: if 'INCLUDEPICTURE' in instr_text.text: return True return False 削除対象の選別 def should_remove_run(run, ns): # フィールド制御要素を持つか確認 has_field_control = (run.find('.//w:fldChar', ns) is not None or run.find('.//w:instrText', ns) is not None) # 実際の画像コンテンツを持つか確認 has_image_content = (run.find('.//w:drawing', ns) is not None or run.find('.//w:pict', ns) is not None) # フィールド制御要素があり、画像コンテンツがない要素を削除 return has_field_control and not has_image_content 結果 このスクリプトにより、問題のあるフィールドコードが除去され、TEI変換プロセスが正常に完了するようになりました。画像は適切に文書内に埋め込まれた状態で保持されます。 ...

校異源氏物語テキストDBに対する検索を行うMCPサーバの構築

校異源氏物語テキストDBに対する検索を行うMCPサーバの構築

概要 校異源氏物語テキストDBに対する検索を行うMCPサーバの構築を行ったので、備忘録です。 一例として、Claude Desktopから以下のように使用することができます。 https://youtu.be/8s2R9V2hEvc?si=p9SW-IvhdNSzwTLN 背景 以下の記事で、校異源氏物語テキストDBに対する検索を行うAPIサーバの構築について紹介しました。 この応用として、MCPサーバを構築することで、AIエージェントとのやりとりを試してみます。 単純なものですが、GitHubのリポジトリは以下です。 https://github.com/nakamura196/genji-mcp-server 開発にあたっては、先述したAPIサーバで提供するOpenAPIの仕様をClaude Codeに渡すことで、テンプレートから効率機に開発してくれました。 なおswagger.jsonには、以下でアクセスいただけます。 https://genji-api.aws.ldas.jp/swagger.json 使い方 npmパッケージを以下で公開しています。 https://www.npmjs.com/package/@nakamura196/genji-mcp-server そこで、以下のような設定ファイルを作成して、Claude Desktopを再起動します。 { "mcpServers": { "genji": { "command": "npx", "args": ["-y", "@nakamura196/genji-mcp-server"] } } } 利用例 「夕顔が含まれる巻は?」などと入力すると、以下のように外部連携を使用するための許可が求められます。許可すると、問い合わせ結果に基づき、AIが回答を作成してくれます。 以下が、応答の例です。 『源氏物語』で夕顔が登場する巻について検索してみますね。検索結果から、夕顔が含まれる巻は以下の通りです: 「夕顔」が登場する巻: 第4巻「夕顔」 - 最も多く登場(7箇所) 第6巻「末摘花」 - 1箇所 第22巻「玉鬘」 - 3箇所 主人公である夕顔の女性は、当然ながら第4巻「夕顔」で中心的に描かれています。この巻では光源氏と夕顔の出会いから悲劇的な結末まが詳しく語られています。 その後、第6巻「末摘花」では回想として言及され、第22巻「玉鬘」では夕顔の娘である玉鬘の物語の中で、母親である夕顔への言及が複数回なされています。 つまり、夕顔という人物は第4巻で主人公として登場し、その後の巻でも重要な人物として言及され続けているということですね。 まとめ MCPサーバの作り方および使い方の確認を主としており、実際には色々と改善の余地がありますが、参考になる部分がありましたら幸いです。より便利な使い方を試してみたいと思います。

校異源氏物語テキストDBに対する検索を行うAPIサーバの構築

校異源氏物語テキストDBに対する検索を行うAPIサーバの構築

概要 校異源氏物語テキストDBに対する検索を行うAPIサーバの構築したので、備忘録です。 https://genji-api.aws.ldas.jp/ 背景 以下のページで、『校異源氏物語』のテキストデータをTEI/XMLに準拠した形で公開しています。 https://kouigenjimonogatari.github.io/ このテキストデータをElasticsearchに登録し、コマごとの検索を可能にするAPIを作成します。 使い方 以下のURLで、OpenAPIおよびSwaggerを用いた使い方の説明ページにアクセスできます。 https://genji-api.aws.ldas.jp/ 工夫点 検索語の展開 例えば以下のURLは、「夕顔」を検索キーワードとした例です。JSON:APIに準拠した入出力形式としています。 https://genji-api.aws.ldas.jp/search?q=夕顔&page[limit]=20&page[offset]=0&sort=page&filter[expandRepeatMarks]=true&filter[unifyKanjiKana]=true&filter[unifyHistoricalKana]=true&filter[unifyPhoneticChanges]=true&filter[unifyDakuon]=true&filter[vol_str]=04 夕顔 この時、以下のような結果が返却されます。入力したキーワード「夕顔」に対して、バリエーションを生成し、これらに基づく検索を行います。 { "data": [], "meta": { "query": "夕顔", "transformedQueries": [ "夕顔", "ゆうかお", "ゆふかお", "ゆふかほ", "ゆうかほ", "夕かお", "夕かほ", "ゆう顔", "ゆふ顔" ], "transformOptions": { "expandRepeatMarks": true, "unifyKanjiKana": true, "unifyHistoricalKana": true, "unifyPhoneticChanges": true, "unifyDakuon": true }, "filters": { "expandRepeatMarks": true, "unifyKanjiKana": true, "unifyHistoricalKana": true, "unifyPhoneticChanges": true, "unifyDakuon": true, "vol_str": "04 夕顔" }, "sort": "page", "limit": 20, "offset": 0, "total": 7, "aggregations": { "vol_str": { "doc_count_error_upper_bound": 0, "sum_other_doc_count": 0, "buckets": [ { "key": "04 夕顔", "doc_count": 7 } ] } } } } その結果、本文中に登場する「ゆふかほ」「夕かほ」「夕顔」を一度に検索することができます。 この検索キーワードの展開については、検索オプションをON/OFFを切り替えられるようにしています。詳細は、上述したSwagger UIでご確認ください。 ...

Minecraft Modでお城生成コマンドを作ってみた【Fabric 1.21 + Claude活用】

Minecraft Modでお城生成コマンドを作ってみた【Fabric 1.21 + Claude活用】

※ 本記事の大部分はAIが執筆しました。 はじめに Minecraft Modの学習を目的として、コマンド一つでお城を生成できるModを作ってみました。 開発環境 今回使用した技術スタック: Minecraft : 1.21 Modding Framework : Fabric Mod Java : 21 開発環境 : Cursor(AI統合エディタ) AI支援 : Claude 4 Sonnet 環境構築 Java 21のインストールが必要だったので、Homebrewで導入: brew install openjdk@21 プロジェクトの初期化は、Fabricの公式テンプレートをクローン: git clone https://github.com/FabricMC/fabric-example-mod.git castle-mod Cloning into 'castle-mod'... remote: Enumerating objects: 930, done. remote: Counting objects: 100% (47/47), done. remote: Compressing objects: 100% (30/30), done. remote: Total 930 (delta 33), reused 17 (delta 17), pack-reused 883 (from 3) Receiving objects: 100% (930/930), 588.77 KiB | 10.51 MiB/s, done. Resolving deltas: 100% (404/404), done. その後、Cursorで開発環境を起動: ...

デジタル文化財管理システム(試行版)のNFT対応

デジタル文化財管理システム(試行版)のNFT対応

お知らせ: 2025-06-14 開発の経過は以下にまとめています。 https://zenn.dev/nakamura196/books/41693d2d017082 概要 以下の記事をはじめとして、ブロックチェーンを用いたデジタル文化財管理システムの試作をしています。 今回、アップロードしたデータがNFTとして認識されるように改修しました。 勉強過程のため、不完全な点があるかと思いますが、参考になりましたら幸いです。 使い方ページ ファイルのアップロード方法はこれまでと同様です。アップロード後に表示される一覧ページにおいて、詳細ページへのリンクを追加しました。 リンクをクリックすると、以下のような詳細画面に遷移します。 実装方法 ※ この章は、AIが執筆しました。 1. コントラクトのNFT対応 既存のデジタル文化財管理コントラクトを、ERC721規格に準拠したNFTコントラクトに改修しました。 主な変更点: 1. OpenZeppelinライブラリの追加 import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721URIStorageUpgradeable.sol"; 2. コントラクトの継承構造を変更 contract DigitalHeritage is Initializable, OwnableUpgradeable, UUPSUpgradeable, ERC721Upgradeable, ERC721URIStorageUpgradeable { // ... } 3. 初期化関数の更新 function initialize() public initializer { __Ownable_init(msg.sender); __UUPSUpgradeable_init(); __ERC721_init("Digital Heritage", "DH"); __ERC721URIStorage_init(); } 4. 文化財登録時のNFTミント機能 function registerHeritage( string memory _name, string memory _description, string memory _imageUrl, string memory _tokenURI ) public { uint256 id = heritages.length; // 文化財データを保存 heritages.push(Heritage({ id: id, name: _name, description: _description, imageUrl: _imageUrl, owner: msg.sender, timestamp: block.timestamp, isDeleted: false })); // NFTとしてミント _safeMint(msg.sender, id); _setTokenURI(id, _tokenURI); emit HeritageRegistered(id, msg.sender, _name); } 2. メタデータ管理システムの実装 NFTの標準的なメタデータ形式に対応するため、サーバーサイドでのメタデータ生成・アップロード機能を実装しました。 ...

ブロックチェーンとPinata IPFSを使用したデジタル文化財管理システムの試作

ブロックチェーンとPinata IPFSを使用したデジタル文化財管理システムの試作

お知らせ: 2025-06-14 開発の経過は以下にまとめています。 https://zenn.dev/nakamura196/books/41693d2d017082 概要 ブロックチェーンの学習にあたり、デジタル文化財の管理システムのプロトタイプを作成しました。ブロックチェーンの学習が目的のため、不足している機能などが多いですが、今後追加・改修を加えていく予定です。 https://digital-heritage-five.vercel.app/ 使用技術 EthereumのSepoliaネットワークを使用しています。ブロックチェーンの学習およびプロトタイプの開発が目的であるため、テストネットワークを使用します。 分散ファイルストレージIPFSのホスティングサービスとして、Pinataを使用しています。 https://pinata.cloud/ 準備 後述する本サイトの使用にあたり、MetaMaskのウォレットの作成や、ETHのSepoliaテストネットの作成などが必要です。また登録にあたっては、ガス代の支払いに必要なSepoliaETHが一定数必要です。 これらの方法については、別の記事で紹介したいと思いますが、インターネット上の記事を参考にしてください。 使い方 以下のURLにアクセスします。 https://digital-heritage-five.vercel.app/ MetaMaskがインストール済みの場合、以下のように表示されます。 「ウォレットを接続」ボタンを押すと、以下の画面が表示されます。 接続後、以下のような画面が表示されます。 サンプルとして、いらすとやさんの画像を利用させていただきます。 https://www.irasutoya.com/2020/12/blog-post_279.html 名前や説明、画像URLを入力して、登録ボタンを押します。 以下の画面が表示されます。確認ボタンを押します。 以下のようにデータが登録されます。 Transactionの確認 Etherscanを使って、取引の内容を確認することができます。 https://sepolia.etherscan.io/tx/0x1234567890abcdef…(例) Input Dataに入力されている文字列は、「スマートコントラクトの関数呼び出しのエンコードされたデータ」とのことです。以下の関数でデコードしてみます。 const ethers = require('ethers'); // デコードするインプットデータ const inputData = '0xb2f262e4...(実際の登録データの例)'; // インプットデータをデコード function decodeInput(input) { try { // 関数シグネチャを取得 const functionSignature = input.slice(0, 10); console.log('Function Signature:', functionSignature); // パラメータデータを取得(最初の4バイトを除く) const paramsData = '0x' + input.slice(10); // デコーダーを作成 const decoder = new ethers.AbiCoder(); // パラメータの型を定義 const types = ['string', 'string', 'string']; // データをデコード const decoded = decoder.decode(types, paramsData); // 結果を表示 console.log('\nDecoded Parameters:'); console.log('Name:', decoded[0]); console.log('Description:', decoded[1]); console.log('Image URL:', decoded[2]); } catch (error) { console.error('デコードエラー:', error); } } // デコードを実行 decodeInput(inputData); 結果、以下のように確認することができました。 ...

Omeka SのIIIF Serverモジュールを使用した階層構造を持つ目次の記述

Omeka SのIIIF Serverモジュールを使用した階層構造を持つ目次の記述

概要 Omeka SのIIIF Serverモジュールを使用し、階層構造を持つ目次の記述方法について紹介します。 マニュアル やや複雑な記述になっていますが、以下に説明があります。 https://github.com/Daniel-KM/Omeka-S-module-IiifServer?tab=readme-ov-file#input-format-of-the-property-for-structures-table-of-contents 単純な目次 「Toyo Bunko Media Repository」で公開されている以下を例とします。 https://app.toyobunko-lab.jp/s/main/document/9f9d95c2-d33d-7402-742b-47db12748688 以下のように、{RangeのID},{Rangeの名前},{Canvasのインデックス}を指定します。 r1,延享、寛延、宝暦,1 r2,明和,25 r3,安永一,55 r4,安永二,79 r5,天明一,110 r6,天明二,142 r7,寛政一,177 r8,寛政二,218 r9,享和,255 r10,文化一,279 r11,文化、文政、二,313 これにより、以下のように目次が表示されます。 階層構造を持つ目次 以下を例とします。 https://app.toyobunko-lab.jp/s/main/document/4797b8ca-530f-8aae-f252-7911ad64039b 階層構造を持つ目次を記述する場合には、以下のように4列でデータを用意します。 r0, 全体,-, r1;r2;r29;r55;r73 r1, 目次, 4-8, - r2, 第一、 蒙疆政府ノ司法制度…一頁, -, r3;r4;r9;r14;r15 r3, 一、 司法制度ノ沿革…一, 8-10, - r4, 二、 司法關係機關…四, -, r5;r6;r7;r8 r5, (一) 司法部…四, 10, - r6, (二) 法院…四, 10-11, - r7, (三) 縣旗司法機關…六, 11, - r8, (四) 行刑機關…七, 11-12, - r9, 三、 司法事務…八, -, r10;r11;r12;r13 r10, (一) 刑事事件…八, 12, - r11, (二) 民事事件…九, 12, - r12, (三) 行刑事務…九, 12-14, - r13, (四) 登記…一二, 14-15, - r14, 四、 司法經費…一四, 15-16, - 4列目に子要素となるRangeのIDを指定します。インデントを含めると、以下のような形になります。 ...

IIIF Presentation API v2のIIIFコレクションで、ページネーションを使う

IIIF Presentation API v2のIIIFコレクションで、ページネーションを使う

概要 IIIF Presentation API v2のIIIFコレクションで、ページネーションを使う機会がありましたので、備忘録です。 背景 IIIFコレクションでは、以下のように、複数のマニフェストファイル(およびコレクション)の一覧を提供することができます。 https://iiif.io/api/presentation/2.1/#collection { "@context": "http://iiif.io/api/presentation/2/context.json", "@id": "http://example.org/iiif/collection/top", "@type": "sc:Collection", "label": "Top Level Collection for Example Organization", "viewingHint": "top", "description": "Description of Collection", "attribution": "Provided by Example Organization", "manifests": [ { "@id": "http://example.org/iiif/book1/manifest", "@type": "sc:Manifest", "label": "Book 1" } ] } この時、対象とするマニフェストファイルが多数になった場合、一つのIIIFコレクションでは配信が難しくなりました。 これに対して、以下でページネーションに関する仕様がありましたので、こちらを使ってみます。 https://iiif.io/api/presentation/2.1/#paging ページネーション 上記のページでは、以下のような例が紹介されていました。 { "@context": "http://iiif.io/api/presentation/2/context.json", "@id": "http://example.org/iiif/collection/top", "@type": "sc:Collection", "label": "Example Big Collection", "total": 9316290, "first": "http://example.org/iiif/collection/c1" } { "@context": "http://iiif.io/api/presentation/2/context.json", "@id": "http://example.org/iiif/collection/c1", "@type": "sc:Collection", "within": "http://example.org/iiif/collection/top", "startIndex": 0, "next": "http://example.org/iiif/collection/c2", "manifests": [ // Manifests live here ... ] } まず、一つの目のJSONでコレクション全体のマニフェストファイル数totalを示し、さらにはじめの部分マニフェストファイル群へのリンクをfirstで提示します。 ...

Drupalで管理者以外のユーザーにも設定画面へのアクセスを許可する方法

Drupalで管理者以外のユーザーにも設定画面へのアクセスを許可する方法

本記事の一部はAIが作成しました。 概要 Drupalでカスタムモジュールの設定画面を作成する際、管理者以外のユーザーにもアクセスを許可したい場合があります。今回は、GitHub Webhookモジュールを例に、この問題の解決方法を説明します。 問題の発生 最初の状態では、以下のようなルーティング設定でした: # github_webhook.routing.yml github_webhook.settings: path: '/admin/config/github_webhook' defaults: _form: '\Drupal\github_webhook\Form\SettingsForm' _title: 'GitHub Webhook Settings' requirements: _permission: 'administer site configuration' この設定では、administer site configuration権限を持つ管理者のみがアクセス可能で、一般ユーザーはアクセスできませんでした。 解決方法1: 専用権限の作成 まず、専用の権限を作成します。github_webhook.permissions.ymlファイルを新規作成: access github webhook settings: title: 'Access GitHub Webhook Settings' description: 'Allow users to access GitHub webhook configuration.' そして、ルーティングファイルを更新: # github_webhook.routing.yml github_webhook.settings: path: '/admin/config/github_webhook' defaults: _form: '\Drupal\github_webhook\Form\SettingsForm' _title: 'GitHub Webhook Settings' requirements: _permission: 'access github webhook settings' 問題の発覚: /adminパスの制限 しかし、この変更だけでは解決しませんでした。Drupalでは/admin配下のパスは管理エリアとして扱われ、追加の権限チェックが行われるためです。 ...

NDL古典籍OCR-Liteを用いたアノテーション付きIIIFマニフェストファイルとTEI/XMLファイルの作成

NDL古典籍OCR-Liteを用いたアノテーション付きIIIFマニフェストファイルとTEI/XMLファイルの作成

お知らせ 本記事で紹介する流れをわかりやすくした記事を作成しました。以下も参考にしてください。 概要 NDL古典籍OCR-Liteを用いたアノテーション付きIIIFマニフェストファイルとTEI/XMLファイルの作成を行うツールを試作したので紹介します。 アノテーション付きIIIFマニフェストファイルの作成 まず、NDL古典籍OCR-Liteを用いて、IIIFマニフェストファイルを入力として、アノテーション付きIIIFマニフェストファイルを出力するGradioアプリを作成しました。Hugging FaceのSpaceを用いて公開しています。 https://nakamura196-ndlkotenocr-lite-iiif.hf.space/ 出力結果として、以下のようなアノテーション付きIIIFマニフェストファイルが得られます。 { "@context": "http://iiif.io/api/presentation/3/context.json", "id": "https://dl.ndl.go.jp/api/iiif/3437686/manifest.json", "type": "Manifest", "label": { "none": [ "校異源氏物語. 巻一" ] }, "items": [ { "id": "https://dl.ndl.go.jp/api/iiif/3437686/canvas/1", "type": "Canvas", "width": 6890, "height": 4706, "label": { "none": [ "1" ] }, "items": [ { "id": "https://dl.ndl.go.jp/api/iiif/3437686/canvas/1/page", "type": "AnnotationPage", "items": [ { "id": "https://dl.ndl.go.jp/api/iiif/3437686/canvas/1/page/imageanno", "type": "Annotation", "motivation": "sc:painting", "target": "https://dl.ndl.go.jp/api/iiif/3437686/canvas/1", "body": { "id": "https://dl.ndl.go.jp/api/iiif/3437686/R0000001/full/full/0/default.jpg", "type": "Image", "format": "image/jpeg", "width": 6890, "height": 4706, "service": [ { "id": "https://dl.ndl.go.jp/api/iiif/3437686/R0000001", "type": "ImageService2", "profile": "level2" } ] } } ] } ], "annotations": [ { "id": "https://dl.ndl.go.jp/api/iiif/3437686/canvas/1/annos", "type": "AnnotationPage", "items": [ { "id": "https://dl.ndl.go.jp/api/iiif/3437686/canvas/1/annos/0", "type": "Annotation", "motivation": "commenting", "target": "https://dl.ndl.go.jp/api/iiif/3437686/canvas/1#xywh=5270,275,114,935", "body": { "type": "TextualBody", "value": "一・〇・・・・・・一一一一・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・" } }, { "id": "https://dl.ndl.go.jp/api/iiif/3437686/canvas/1/annos/1", "type": "Annotation", "motivation": "commenting", "target": "https://dl.ndl.go.jp/api/iiif/3437686/canvas/1#xywh=5293,2009,218,424", "body": { "type": "TextualBody", "value": "○〇" } }, { "id": "https://dl.ndl.go.jp/api/iiif/3437686/canvas/1/annos/2", "type": "Annotation", "motivation": "commenting", "target": "https://dl.ndl.go.jp/api/iiif/3437686/canvas/1#xywh=5092,3272,63,80", "body": { "type": "TextualBody", "value": "一一" } }, { "id": "https://dl.ndl.go.jp/api/iiif/3437686/canvas/1/annos/3", "type": "Annotation", "motivation": "commenting", "target": "https://dl.ndl.go.jp/api/iiif/3437686/canvas/1#xywh=4375,304,103,1475", "body": { "type": "TextualBody", "value": "ス〇〇〇六〇〇〇一〇〇〇〇〇〇〇一一一〇〇〇一一一一〇〇〇〇〇〇〇〇〇〇一一・〇〇・・・・・・・の〇〇・・・・一・・・" } }, { "id": "https://dl.ndl.go.jp/api/iiif/3437686/canvas/1/annos/4", "type": "Annotation", "motivation": "commenting", "target": "https://dl.ndl.go.jp/api/iiif/3437686/canvas/1#xywh=4375,2853,45,522", "body": { "type": "TextualBody", "value": "□琉球□□□□□□□□□□□□□□□□□" } }, { "id": "https://dl.ndl.go.jp/api/iiif/3437686/canvas/1/annos/5", "type": "Annotation", "motivation": "commenting", "target": "https://dl.ndl.go.jp/api/iiif/3437686/canvas/1#xywh=4283,2756,63,252", "body": { "type": "TextualBody", "value": "〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇〇一〇〇一〇〇〇" } }, { "id": "https://dl.ndl.go.jp/api/iiif/3437686/canvas/1/annos/6", "type": "Annotation", "motivation": "commenting", "target": "https://dl.ndl.go.jp/api/iiif/3437686/canvas/1#xywh=694,499,310,2991", "body": { "type": "TextualBody", "value": "同校異源氏物巻一" } } ] } ] }, { "id": "https://dl.ndl.go.jp/api/iiif/3437686/canvas/2", "type": "Canvas", "width": 6890, "height": 4706, "label": { "none": [ "2" ] }, "items": [ { "id": "https://dl.ndl.go.jp/api/iiif/3437686/canvas/2/page", "type": "AnnotationPage", "items": [ { "id": "https://dl.ndl.go.jp/api/iiif/3437686/canvas/2/page/imageanno", "type": "Annotation", "motivation": "sc:painting", "target": "https://dl.ndl.go.jp/api/iiif/3437686/canvas/2", "body": { "id": "https://dl.ndl.go.jp/api/iiif/3437686/R0000002/full/full/0/default.jpg", "type": "Image", "format": "image/jpeg", "width": 6890, "height": 4706, "service": [ { "id": "https://dl.ndl.go.jp/api/iiif/3437686/R0000002", "type": "ImageService2", "profile": "level2" } ] } } ] } ], "annotations": [ { "id": "https://dl.ndl.go.jp/api/iiif/3437686/canvas/2/annos", "type": "AnnotationPage", "items": [] } ] } ] } TEI/XMLファイルの作成 上記で得られたアノテーション付きIIIFマニフェストファイルを入力として、TEI/XMLファイルを作成するライブラリを作成しました。 ...

DrupalでJSON形式のFieldを扱うための「JSON Field」モジュールを使用する

DrupalでJSON形式のFieldを扱うための「JSON Field」モジュールを使用する

概要 DrupalでJSON形式のFieldを扱うための「JSON Field」モジュールを使用する機会がありましたので、備忘録です。 https://www.drupal.org/project/json_field 結果、以下のようにエディタと共にJSONを扱えるようになりました。 インストール 以下により、ダウンロードします。 composer require 'drupal/json_field:^1.4' drush en json_field さらに、以下により、ウィジェットも有効します。 drush en json_field_widget GUIから有効にする場合には、以下の2つを有効にします。 設定 コンテンツタイプのフィールドの管理において、JSONフィールドを追加します。 そして、「フォームの表示管理」において、ウィジェットを選択します。 結果、コンテンツの編集画面で、以下のようなフォームが表示されます。 まとめ DrupalでJSONを管理するにあたり、参考になりましたら幸いです。

TEI/XMLファイルをS3互換のオブジェクトストレージでホストする

TEI/XMLファイルをS3互換のオブジェクトストレージでホストする

概要 TEI/XMLファイルをS3互換のオブジェクトストレージでホストする機会がありましたので、備忘録です。具体的には、mdx Iのオブジェクトストレージを対象にします。 https://mdx.jp/mdx1/p/about/system 背景 TEI/XMLファイルを読み込み、その内容を可視化するウェブアプリケーション(Next.js)を構築します。この時、ファイル数やサイズが小さい場合は、publicフォルダに格納していましたが、これらが大きくなった場合、別の場所でホストすることを考えました。 場所の選択肢は多々ありますが、今回はS3互換であるmdx Iのオブジェクトストレージを対象にします。 GUIを用いたオブジェクトストレージへのファイルアップロード オブジェクトストレージへTEI/XMLファイルをGUI経由でアップロードする方法も多々あります。その中で、これまではCyberduckを使用する方法や、GakunNin RDMを使用する方法などを紹介しました。 一方、今回の事例では、TEI/XML以外のコンテンツをDrupalで管理していました。そこで、Drupalとオブジェクトストレージを接続し、ユーザはDrupalの操作で完結できるようにしました。 Drupalとオブジェクトストレージの接続 以下のモジュールを使用します。 https://www.drupal.org/project/s3fs インストール後、環境設定のページ/admin/configから、S3 File Systemを選択します。 そして、アクセスキーや秘密鍵を登録し、さらにS3のバケット名を登録します。 またAdvanced Configuration OptionsのCustom Host Settingsにおいて、https://s3ds.mdx.jpを入力します。 これでオブジェクトストレージとの接続設定は完了です。 その後、各コンテンツタイプのフィード設定において、アップロード先として「S3 File System」を選択します。 また、今回はTEI/XMLファイルがアップロード対象となるため、「許可されている拡張子」として、xmlを入力します。 この結果、DrupalのGUIを介してアップロードしたTEI/XMLファイルが、mdx Iのオブジェクトストレージに格納されるようになりました。 (参考)DrupalのJSON:APIを用いたファイルの一括アップロード TEI/XMLの初期登録にあたり、Pythonを用いた一括登録を行いました。JSON:APIを用いたファイルの一括アップロードの方法は、以下の記事などが参考になりました。 https://www.drupal.org/node/3024331 一例ですが、以下のようなスクリプトで実現できました。 import requests import json import os from dotenv import load_dotenv from glob import glob from tqdm import tqdm class ApiClient: def __init__(self): load_dotenv(override=True) # DrupalサイトのURL(例) self.DRUPAL_BASE_URL = os.getenv("DRUPAL_BASE_URL") # エンドポイント(JSON:API) # self.JSONAPI_ENDPOINT = f"{self.DRUPAL_BASE_URL}/jsonapi/node/article" # 認証情報(Basic認証) self.USERNAME = os.getenv("DRUPAL_USERNAME") self.PASSWORD = os.getenv("DRUPAL_PASSWORD") def login(self): # ログインリクエスト login_url = f"{self.DRUPAL_BASE_URL}/user/login?_format=json" login_response = requests.post( login_url, json={"name": self.USERNAME, "pass": self.PASSWORD}, headers={"Content-Type": "application/json"} ) if login_response.status_code == 200: self.session_cookies = login_response.cookies def get_csrf_token(self): # CSRFトークンを取得 csrf_token_response = requests.get( f"{self.DRUPAL_BASE_URL}/session/token", cookies=self.session_cookies # ここでログインセッションを渡す ) if csrf_token_response.status_code == 200: # return csrf_token_response.text # self.csrf_token = csrf_token_response.text self.headers = { "Content-Type": "application/vnd.api+json", "Accept": "application/vnd.api+json", "X-CSRF-Token": csrf_token_response.text, } else: # raise Exception(f"CSRFトークン取得失敗: {csrf_token_response.status_code} {csrf_token_response.text}") self.csrf_token = None def upload_file(self, type, uuid, field, file_path, verbose=False): url = f"{self.DRUPAL_BASE_URL}/jsonapi/node/{type}/{uuid}/{field}" # ファイル名を取得 filename = os.path.basename(file_path) # ファイルをバイナリモードで読み込む with open(file_path, 'rb') as f: file_data = f.read() headers = self.headers.copy() headers['Content-Type'] = 'application/octet-stream' headers['Content-Disposition'] = f'attachment; filename="{filename}"' # ファイルをアップロード response = requests.post(url, headers=headers, cookies=self.session_cookies, data=file_data) if response.status_code == 200: if verbose: print(f"ファイルアップロード成功: {filename}") else: print(f"ファイルアップロード失敗: {response.status_code} {response.text}") すでに対象コンテンツが作成済みで、例えばfield_fileといったフィールドにファイルをアップロードする目的で使用することができます。 ...

校異源氏物語テキストDBのDTS(Distributed Text Services) APIの更新

校異源氏物語テキストDBのDTS(Distributed Text Services) APIの更新

概要 校異源氏物語テキストDBのDTS(Distributed Text Services) APIを更新したので、備忘録です。 背景 DTS(Distributed Text Services) APIは以下で説明されています。 https://distributed-text-services.github.io/specifications/ 以下の記事で、DTS APIの作成について紹介しました。 一方、以下を課題としていました。 今回開発したDTS APIも上記のガイドラインに非対応の箇所がある可能性がある点にご注意ください。 そこで、前回作成したAPIをv1とし、今回はdtsVersionの1-alphaに従ったv2のAPIを作成します。 API 以下がEntry Endpointです。v1とv2の違いは以下です。 v1 https://dts-typescript.vercel.app/api/v1/dts { "navigation": "/api/v1/dts/navigation", "@id": "/api/v1/dts", "@type": "EntryPoint", "collections": "/api/v1/dts/collections", "@context": "dts/EntryPoint.jsonld", "documents": "/api/v1/dts/document" } v2 https://dts-typescript.vercel.app/api/v2/dts { "@context": "https://distributed-text-services.github.io/specifications/context/1-alpha1.json", "dtsVersion": "1-alpha", "@id": "/api/v2/dts", "@type": "EntryPoint", "collection": "/api/v2/dts/collection{?id}", "navigation": "/api/v2/dts/navigation{?resource,ref,down}", "document": "/api/v2/dts/document{?resource,ref}" } 同様に、各種Endpointの記述を変更しています。 ビューアの改修 以下の記事で、DTSのビューア開発について紹介しました。 そして、以下を課題としていましたが、この点に対応できるように改修しました。 Navigation Endpointを使用していますが、現時点で複数階層には非対応です。 例えば、Navigation Endpointは以下のように記述します。 https://dts-typescript.vercel.app/api/v2/dts/navigation?resource=urn:kouigenjimonogatari.1&down=1 { "@context": "https://distributed-text-services.github.io/specifications/context/1-alpha1.json", "dtsVersion": "1-alpha", "@type": "Navigation", "@id": "/api/v2/dts/navigation?resource=urn:kouigenjimonogatari.1&down=1", "resource": { "@id": "urn:kouigenjimonogatari.1", "@type": "Resource", "document": "/api/v2/dts/document?resource=urn:kouigenjimonogatari.1{&ref}", "collection": "/api/v2/dts/collection?id=urn:kouigenjimonogatari.1", "navigation": "/api/v2/dts/navigation?resource=urn:kouigenjimonogatari.1{&ref}", "citationTrees": [ { "@type": "CitationTree", "citeStructure": [ { "@type": "CiteStructure", "citeType": "page", "citeStructure": [ { "@type": "CiteStructure", "citeType": "line" } ] } ] } ] }, "member": [ { "identifier": "5", "@type": "CitableUnit", "level": 1, "parent": null, "citeType": "page" }, { "identifier": "6", "@type": "CitableUnit", "level": 1, "parent": null, "citeType": "page" }, { "identifier": "7", "@type": "CitableUnit", "level": 1, "parent": null, "citeType": "page" }, { "identifier": "8", "@type": "CitableUnit", "level": 1, "parent": null, "citeType": "page" }, ... ] } 特に、CitationTreeを使って、階層を記述します。ビューアがこの情報を処理するように修正することで、以下のように、レベルごとのナビゲーションボタンが表示されるようにしました。 ...

Dockerによるディスク圧迫の調査と対処法【Ubuntu 22.04 運用事例】

Dockerによるディスク圧迫の調査と対処法【Ubuntu 22.04 運用事例】

はじめに 本記事では、Dockerコンテナやイメージによるディスク圧迫が原因でElasticsearchにエラーが発生した事例と、その調査・対処方法について記録します。同様の問題に直面している方の参考になれば幸いです。 🔍 問題の発生 運用中のElasticsearchで以下のエラーが発生しました。 { "error": { "type": "search_phase_execution_exception", "reason": "all shards failed", "phase": "query", ... }, "status": 503 } 初期調査により、インデックスが close 状態になっており、ディスク容量不足が疑われました。 📊 ディスク使用状況の調査 ルートディレクトリの使用量確認 まず、システム全体のディスク使用状況を確認しました。 sudo du -h --max-depth=1 / | sort -hr | head -n 20 実行結果: 60G / 50G /var 4.7G /usr 2.1G /home 1.2G /opt ... /var ディレクトリが50GBと異常に大きいことが判明しました。 /var ディレクトリの詳細調査 sudo du -h --max-depth=1 /var | sort -hr 実行結果: 50G /var 49G /var/lib 342M /var/log 240M /var/cache 128M /var/spool ... /var/lib がほぼ全容量を占めているため、さらに詳細を調査しました。 sudo du -h --max-depth=1 /var/lib | sort -hr 実行結果: ...

Annotorious v2のpolygonツールを使って、polylineを作成する

Annotorious v2のpolygonツールを使って、polylineを作成する

概要 Annotorious v2のpolygonツールを使って、polylineを作成する方法の備忘録です。 背景 Annotorious v2のウェブサイトは以下です。 https://annotorious.github.io/getting-started/ 以下のように、polygonを記述することができます。 一方、同様の方法でpolylineを記述するツールは、以下のプラグインを含めて、提供されていないようでした。 https://github.com/annotorious/annotorious-v2-selector-pack カスタマイズ 以下のような多角形を作成した場合、 以下のようなJSONファイルが作成されます。 { "type": "Annotation", "body": [ { "type": "TextualBody", "value": "polygon", "purpose": "commenting" } ], "target": { "source": "https://www.e-codices.unifr.ch/loris/gau/gau-Fragment/gau-Fragment_frag001a.jp2/full/full/0/default/jpg", "selector": { "type": "SvgSelector", "value": "<svg><polygon points=\"3383.121337890625,1290.137451171875 945.135498046875,1658.426513671875 885.9696655273438,3003.352294921875 2508.54150390625,3348.424072265625 3485.021484375,2724.35791015625 2170.811767578125,2107.6337890625\" /></svg>" } }, "@context": "http://www.w3.org/ns/anno.jsonld", "id": "#c469b1a3-8902-4443-8f54-47df8bb87d7e" } 上記に対して、autoCloseのような変数を用意し、これがfalseの場合、polygonという文字列をpolylineに変更する処理を加えました。 anno.on("createAnnotation", function (selection: any) { if(!autoClose.value) { selection.target.selector.value = selection.target.selector.value.replace("polygon", "polyline"); } ... }); これにより、以下のように、polygonツールをベースとして、polygonとpolylineを使い分けることができます。 TEI/XMLでの記述 TEI/XMLでの多角形の記述例として、path要素を使用することができます。この場合、polygonであれば始点を終点の後に追加することで、多角形を表現することができます。 <facsimile> <surface ulx="0" uly="0" lrx="8176" lry="6132"> <graphic url="https://www.e-codices.unifr.ch/loris/gau/gau-Fragment/gau-Fragment_frag001a.jp2/full/full/0/default/jpg" /> <zone xml:id="layer_000" change="#ch_layer_000" n="layer_01" type="layer"> <zone xml:id="sign_layer_000_0000" change="#ch_sign_layer_000_0000" type="sign"> <!-- polygon --> <path points="1290.137451171875,3383.121337890625 1658.426513671875,945.135498046875 3003.352294921875,885.9696655273438 3348.424072265625,2508.54150390625 2724.35791015625,3485.021484375 2107.6337890625,2170.811767578125 1290.137451171875,3383.121337890625"></path> </zone> <zone xml:id="sign_layer_000_0001" change="#ch_sign_layer_000_0001" type="sign"> <!-- polyline --> <path points="1393.265625,5290.81005859375 1921.02783203125,3869.745849609375 2982.731689453125,3829.64013671875 3428.122802734375,4874.005859375 2683.244384765625,5741.7509765625 2138.024169921875,4582.19775390625"></path> </zone> </zone> </surface> </facsimile> アプリケーションによっては、始点と終点が一致しなくても閉じた図形を描くことがあるかもしれませんが、polygonとpolylineを使い分ける方法として、参考になりましたら幸いです。 ...