ホーム 記事一覧 ブック DH週間トピックス 検索 このサイトについて
English

最新の記事

Omeka SのImage ServerでのCORS対応

Omeka SのImage ServerでのCORS対応

概要 Omeka SのImage ServerでのCORS対応に関する備忘録です。 背景 以下の記事で、Omeka SのIIIF ServerモジュールでのCORSエラーへの対応方法を紹介しました。 上記の設定により、IIIFマニフェストファイルはダウンロードできるようになりましたが、以下のように、画像がダウンロードできなくなるケースがありました。 Access to image at 'https://xxx/iiif/2/8455/full/86,/0/default.jpg' from origin 'https://uv-v4.netlify.app' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains multiple values '*, *', but only one is allowed. この症状への対処方法について紹介します。 方法 複数の箇所にaccess-control-allow-originが記載されていることが原因です。 上記の記事の対応により、サイト全体に対してCORSの設定を行いましたので、Image Serverモジュールにおける設定が適用されないように修正します。なお、このような対応が必要なものは特定のバージョンのモジュールで、最新のモジュールなどを使用する場合には不要かもしれません。 具体的には、以下のファイルに記載されています。 https://github.com/Daniel-KM/Omeka-S-module-ImageServer/blob/master/src/Controller/ImageController.php 以下のように記載されており、条件によって適用の可否が設定されていますが、使用しているバージョンや他のモジュールとの組み合わせにより、この条件が適用されず、複数のaccess-control-allow-originが設定されてしまうことが原因かと思われます。 $headers->addHeaderLine('Access-Control-Allow-Origin', '*'); 上記のような記載をコメントアウトすることにより、'Access-Control-Allow-Origin' header contains multiple values '*, *'というエラーを解消することができました。 まとめ 同様の事象の遭遇することは稀かもしれませんが、Omeka Sを用いたIIIF画像配信にあたり、参考になりましたら幸いです。

Alfrescoのファイルに対して、Archivematicaを使ってAIPを作成する

Alfrescoのファイルに対して、Archivematicaを使ってAIPを作成する

概要 Alfrescoのファイルに対して、Archivematicaを使ってAIPを作成する方法の一例です。 以下が成果物のデモ動画です。 https://youtu.be/7WCO7JoMnWc システム構成 今回は以下のようなシステム構成とします。複数のクラウドサービスを利用していることに特に意味はありません。 Alfrescoは、以下の記事を参考に、Azure上に構築したものを使用します。 Archivematicaとオブジェクトストレージはmdx.jpを使用し、分析環境はGakuNin RDMを使用します。 オブジェクトストレージへのファイルアップロード Alfrescoからファイルをダウンロード Alfrescoからのファイルダウンロードにあたっては、REST APIを使用します。 https://docs.alfresco.com/content-services/6.0/develop/rest-api-guide/ OpenAPIに準拠しており、以下などを参考にしました。 https://api-explorer.alfresco.com/api-explorer/ 例えば以下により、Alfrescoのユーザ名とパスワード、およびホスト名を環境変数から読み込み、メタデータの取得やコンテンツのダウンロードを行うことができました。 # %% ../nbs/00_core.ipynb 3 from dotenv import load_dotenv import os import requests from base64 import b64encode # %% ../nbs/00_core.ipynb 4 class ApiClient: def __init__(self, verbose=False): """Alfresco API Client Args: verbose (bool): デバッグ情報を出力するかどうか """ self.verbose = verbose # .envの読み込み load_dotenv(override=True) # 環境変数の取得 self.user = os.getenv('ALF_USER') self.password = os.getenv('ALF_PASSWORD') self.target_host = os.getenv('ALF_TARGET_HOST') self._debug("環境変数の設定:", { "user": self.user, "password": "*" * len(self.password) if self.password else None, "target_host": self.target_host }) # Basic認証のヘッダーを作成 credentials = f"{self.user}:{self.password}" encoded_credentials = b64encode(credentials.encode()).decode() self.headers = { 'accept': 'application/json', 'authorization': f'Basic {encoded_credentials}' } self._debug("ヘッダーの設定:", { "accept": self.headers['accept'], "authorization": "Basic ***" }) def _debug(self, message: str, data: dict = None): """デバッグ情報を出力する Args: message (str): メッセージ data (dict, optional): 追加のデータ """ if self.verbose: print(f"🔍 {message}") if data: for key, value in data.items(): print(f" - {key}: {value}") def get_nodes_nodeId(self, node_id: str): """ノードIDでノード情報を取得する Args: node_id (str): ノードID Returns: dict: ノード情報 """ url = f"{self.target_host}/alfresco/api/-default-/public/alfresco/versions/1/nodes/{node_id}" self._debug("APIリクエスト:", {"url": url}) try: response = requests.get( url, headers=self.headers, timeout=float(30) ) response.raise_for_status() return response.json() except requests.exceptions.Timeout: self._debug("エラー:", {"type": "timeout", "message": "リクエストがタイムアウトしました"}) return None except requests.exceptions.RequestException as e: self._debug("エラー:", {"type": "request", "message": str(e)}) return None def get_nodes_nodeId_content(self, node_id: str, output_path: str): """ノードのコンテンツを取得する Args: node_id (str): ノードID output_path (str): 出力パス """ url = f"{self.target_host}/alfresco/api/-default-/public/alfresco/versions/1/nodes/{node_id}/content" self._debug("APIリクエスト:", { "url": url, "output_path": output_path }) response = requests.get(url, headers=self.headers) binary_data = response.content os.makedirs(os.path.dirname(output_path), exist_ok=True) with open(output_path, "wb") as file: file.write(binary_data) self._debug("ファイル保存完了:", { "size": len(binary_data), "path": output_path }) オブジェクトストレージにファイルをアップロード boto3と、オブジェクトストレージのENDPOINT_URL、ACCESS_KEY、SECRET_KEYおよびBUCKET_NAMEなどを使用して、ファイルのアップロード(とダウンロード)を行います。 ...

Alfresco Governance Services Community Editionを起動する

Alfresco Governance Services Community Editionを起動する

概要 以下のマニュアルを参考に、Alfresco Governance Services Community Editionのインストールを試みましたので、備忘録です。 https://support.hyland.com/r/Alfresco/Alfresco-Governance-Services-Community-Edition/23.4/Alfresco-Governance-Services-Community-Edition 参考 同様の取り組みとして、以下があります。こちらも参考にしてください。 https://irisawadojo.blogspot.com/2020/11/72alfresco2.html 仮想マシン 以下のマシンをAzureの仮想マシンとして作成しました。 イメージ: Ubuntu Server 24.04 LTS - Gen2 VM アーキテクチャ: x64 サイズ: Standard D2ads v6 (2 vcpu 数、8 GiB のメモリ) 8080ポートを使用するために、ポートを開けておきます。 Dockerのインストール Dockerをインストールします。 sudo apt-get update sudo apt-get install -y ca-certificates curl gnupg # Docker の公式 GPG キーを追加 sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo tee /etc/apt/keyrings/docker.asc > /dev/null sudo chmod a+r /etc/apt/keyrings/docker.asc # Docker のリポジトリを追加 echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt-get update # Docker をインストール sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin # sudo なしで Docker を実行 sudo usermod -aG docker $USER newgrp docker # Docker の動作確認 docker run hello-world インストール 以下の「Install using Docker Compose」を使用します。 ...

XSLTを使いながらTEI/XMLファイルを編集する

XSLTを使いながらTEI/XMLファイルを編集する

概要 XSLTを使いながらTEI/XMLファイルを編集する方法の一例を紹介します。 関連 以下の記事で、VSCodeの拡張機能を使いながら、XSLTの結果をプレビューする方法を紹介しました。 本記事では、上記の拡張機能を使用せず、より単純にXSLTを使いながらTEI/XMLファイルを編集する方法を紹介します。 拡張機能のインストール VSCodeで以下の拡張機能をインストールします。 Live Server https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer Scholarly XML https://marketplace.visualstudio.com/items?itemName=raffazizzi.sxml Auto Close Tag https://marketplace.visualstudio.com/items?itemName=formulahendry.auto-close-tag 加えて、以下の2つの拡張機能が、Scholarly XMLにおいて推奨されています。ただ、私の使い方では不便になってしまう場面もあったので、とりあえず任意とします。 Auto Rename Tag https://marketplace.visualstudio.com/items?itemName=formulahendry.auto-rename-tag Close HTML/XML tag https://marketplace.visualstudio.com/items?itemName=Compulim.compulim-vscode-closetag ファイルのダウンロードなど 以下のサンプルリポジトリを対象にします。 https://github.com/nakamura196/tei-xml-xslt-vscode 以下により、ファイルをダウンロードします。そして、VSCodeで開きます。 git clone https://github.com/nakamura196/tei-xml-xslt-vscode code tei-xml-xslt-vscode そして、画面右下のGo Liveをクリックします。 デフォルトでは、http://127.0.0.1:5500/がブラウザで開かれ、以下のフォルダが表示されます。 そして、teiフォルダ内のexample1.xmlを開くと、以下のように表示されます。 http://127.0.0.1:5500/tei/example1.xml TEI/XMLファイルの編集 VSCodeでTEI/XMLファイルを編集します。 そして、ブラウザで開いているページを更新すると、編集内容が反映されます。 これを繰り返し、TEI/XMLファイルを編集しながらプレビュー結果を確認します。 ファイルの変更 example1.xmlをコピーすることにより、新規のファイルを作成することができます。 またxslファイルは以下のように相対パスで指定されています。 <?xml-stylesheet type="text/xsl" href="../xsl/make-CETEIcean.xsl"?> 新規にxslファイルを作成し、上記の相対パスを変更することにより、他のxslファイルを用いたXSLTを実行できます。 参考 VSCodeでTEI/XMLファイルを編集する場合、Github Copilotなどを使って、AIによるサジェストを受けることができます。 またCursorを使って、AIによるアシストを受けることもできます。XSLファイルの編集にあたっては、AIによるアシストが特に便利でした。 縦書きにしたい。のような自然言語で指示することにより、xslファイルを編集できました。 まとめ 上記の手順はあくまで一例ですが、TEI/XMLファイルの編集にあたり、参考になりましたら幸いです。

DataverseのデータをArchivematicaで処理する

DataverseのデータをArchivematicaで処理する

概要 DataverseのデータをArchivematicaで処理する流れを確認しましたので、備忘録です。 背景 ArchivematicaではDataverseのデータを入力する機能を提供しています。 https://www.archivematica.org/en/docs/archivematica-1.17/user-manual/transfer/dataverse/ 本機能について、以下の講演会で教えていただいたので、実際に試してみました。 https://www.kulib.kyoto-u.ac.jp/bulletin/1402322 Dataverse 以下の記事でも使用したDemo Dataverseを使用します。 以下のデータをアップロードしました。 https://demo.dataverse.org/dataset.xhtml?persistentId=doi:10.70122/FK2/IHQZL3 ここから画像データそのものと、JSONデータをダウンロードします。Metadataタブに移動し、Export MetadataからJSONを選択します。 以下はJSONファイルの一部ですが、metadataBlocksにメタデータ、filesに画像ファイルの情報が記載されています。 { "metadataBlocks": { "citation": { "displayName": "Citation Metadata", "name": "citation", "fields": [ { "typeName": "title", "multiple": false, "typeClass": "primitive", "value": "nakamura196" }, { "typeName": "author", "multiple": true, "typeClass": "compound", "value": [ { "authorName": { "typeName": "authorName", "multiple": false, "typeClass": "primitive", "value": "Nakamura, Satoru" }, "authorAffiliation": { "typeName": "authorAffiliation", "multiple": false, "typeClass": "primitive", "value": "https://ror.org/057zh3y96", "expandedvalue": { "scheme": "http://www.grid.ac/ontology/", "termName": "The University of Tokyo", "@type": "https://schema.org/Organization" } } } ] }, { "typeName": "datasetContact", "multiple": true, "typeClass": "compound", "value": [ { "datasetContactName": { "typeName": "datasetContactName", "multiple": false, "typeClass": "primitive", "value": "Nakamura, Satoru" }, "datasetContactEmail": { "typeName": "datasetContactEmail", "multiple": false, "typeClass": "primitive", "value": "na.kamura.1263@gmail.com" } } ] }, { "typeName": "dsDescription", "multiple": true, "typeClass": "compound", "value": [ { "dsDescriptionValue": { "typeName": "dsDescriptionValue", "multiple": false, "typeClass": "primitive", "value": "My First Dataset" } } ] }, { "typeName": "subject", "multiple": true, "typeClass": "controlledVocabulary", "value": [ "Arts and Humanities" ] }, { "typeName": "depositor", "multiple": false, "typeClass": "primitive", "value": "Nakamura, Satoru" }, { "typeName": "dateOfDeposit", "multiple": false, "typeClass": "primitive", "value": "2025-01-19" } ] } }, "files": [ { "label": "nakamura196.jpg", "restricted": false, "version": 1, "datasetVersionId": 281093, "dataFile": { "id": 2514724, "persistentId": "doi:10.70122/FK2/IHQZL3/B7JVQS", "pidURL": "https://doi.org/10.70122/FK2/IHQZL3/B7JVQS", "filename": "nakamura196.jpg", "contentType": "image/jpeg", "friendlyType": "JPEG Image", "filesize": 53656, "storageIdentifier": "s3://demo-dataverse-org:1948154820d-63733533ea7c", "rootDataFileId": -1, "md5": "72f08a8b07bacbe3b5cf021910fd26dc", "checksum": { "type": "MD5", "value": "72f08a8b07bacbe3b5cf021910fd26dc" }, "tabularData": false, "creationDate": "2025-01-19", "publicationDate": "2025-01-19", "fileAccessRequest": true } } ] } データの準備 Dataverseのサンプルデータは以下に格納されています。 ...

VSCodeとXSLTを用いたTEI/XMLのリアルタイムプレビュー

VSCodeとXSLTを用いたTEI/XMLのリアルタイムプレビュー

概要 VSCodeとXSLTを用いたTEI/XMLのリアルタイムプレビュー環境を試作したので、備忘録です。 挙動 動作例は以下です。TEI/XMLファイルを編集し、保存すると、ブラウザの表示内容が更新されます。 https://youtu.be/ZParCRUc5AY?si=-aHHi3bIZGWoJYnP 準備 以下の拡張機能をインストールします。 Live Server Trigger Task on Save TEI/XMLを保存した際に、Trigger Task on SaveによってXSLTを実行し、変換されたHTMLファイルをLive Serverで閲覧します。 リポジトリ サンプルコードを以下に格納しています。 https://github.com/nakamura196/tei-xml-xslt-vscode XSLTを行うにあたり、xslt3をインストールします。 git clone https://github.com/nakamura196/tei-xml-xslt-vscode cd tei-xml-xslt-vscode npm install settings.jsonとtasks.json .vscodeフォルダに、settings.jsonとtasks.jsonを格納しています。 tasks.jsonは以下です。xsl/make-CETEIcean.xslを用いて、XSLTを行うタスクを設定しています。 { "version": "2.0.0", "tasks": [ { "label": "Transform XML with XSLT", "type": "shell", "command": "npx", "args": [ "xslt3", "-xsl:xsl/make-CETEIcean.xsl", "-s:${file}", "-o:${fileDirname}/${fileBasenameNoExtension}.html" ], "presentation": { "reveal": "never", "close": true }, "group": { "kind": "build", "isDefault": true }, "problemMatcher": [] } ] } xsl/make-CETEIcean.xslは、CETEIceanで公開されているXSLファイルです。 https://github.com/TEIC/CETEIcean/blob/master/xslt/make-CETEIcean.xsl 次に、settings.jsonです。teiフォルダ以下のxmlファイルに対して、保存時に上記のタスクを実行します。 { "triggerTaskOnSave.tasks": { "Transform XML with XSLT": ["tei/*.xml"] } } これらの設定により、TEI/XMLファイルを保存時に、同フォルダにHTMLファイルが作成されます。 ...

校異源氏物語テキストDBのTEI/XMLからPDFを作成する

校異源氏物語テキストDBのTEI/XMLからPDFを作成する

概要 校異源氏物語テキストDBは、『校異源氏物語』のテキストデータを公開するデータベースです。 https://kouigenjimonogatari.github.io/ 今回、本DBに以下のようなPDFファイルを追加しました。 https://kouigenjimonogatari.github.io/output/01/main.pdf 本記事は、上記のようなPDFファイルを、XSLTとTeXを使って作成します。 リポジトリのクローン 以下のように、リポジトリをクローンします。 git clone --depth 1 https://github.com/kouigenjimonogatari/kouigenjimonogatari.github.io そして以下のコマンドにより、xslt3をインストールします。 npm i xslt3 https://www.npmjs.com/package/xslt3 XSLファイルの作成 今回は、まずTEI/XMLファイルをTeXファイルに変換します。 以下のようなXSLファイルを作成しました。 https://github.com/kouigenjimonogatari/kouigenjimonogatari.github.io/blob/master/xsl/tex.xsl <?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:tei="http://www.tei-c.org/ns/1.0"> <xsl:output method="text" encoding="UTF-8"/> <xsl:template match="/"> \documentclass[a4paper,11pt,landscape]{ltjtarticle} \usepackage{xcolor} \usepackage{luatexja-fontspec} % fontspec を LuaTeX-ja と共に利用 \usepackage[top=2cm,bottom=2cm,left=2cm,right=2cm,textwidth=25cm]{geometry} % スタイル定義 \newcommand{\person}[1]{\textbf{\color{blue}#1}} \newcommand{\place}[1]{\textit{\color{green}#1}} % 日本語フォント設定 \setmainjfont{Noto Serif CJK JP} % 日本語フォントを指定 \title{<xsl:value-of select="//tei:title"/>} \date{} \begin{document} \maketitle % 本文 <xsl:for-each select="//tei:seg"> <xsl:apply-templates/> \par\medskip </xsl:for-each> \end{document} </xsl:template> <!-- 人名の処理 --> <xsl:template match="tei:persName"> \person{<xsl:value-of select="."/>}</xsl:template> <!-- 地名の処理 --> <xsl:template match="tei:placeName"> \place{<xsl:value-of select="."/>}</xsl:template> </xsl:stylesheet> 先にインストールしたxslt3を使って、以下のようにTEI/XMLファイルをTeXファイルに変換できます。 ...

@sidebase/nuxt-authのローカル認証を試す

@sidebase/nuxt-authのローカル認証を試す

概要 @sidebase/nuxt-authのローカル認証を試す機会がありましたので、備忘録です。 背景 以下の記事で、@sidebase/nuxt-authを使って、Drupalの認証を行う方法を紹介しました。 上記の記事では、Nuxt3のSSRを利用して、@sidebase/nuxt-authのauthjsプロバイダを使用していました。プロバイダの説明は以下です。 authjs: for non-static apps that want to use Auth.js / NextAuth.js to offer the reliability & convenience of a 23k star library to the Nuxt 3 ecosystem with a native developer experience (DX) local: for static pages that rely on an external backend with a credential flow for authentication. The Local Provider also supports refresh tokens since v0.9.0. Read more here. (機械翻訳)authjs: 非静的なアプリ向けで、Auth.js / NextAuth.js を使用し、23,000以上のスターを持つ信頼性と利便性をNuxt 3エコシステムに提供します。開発者にネイティブな開発体験 (DX) を提供します。 local: 外部バックエンドを使用し、認証のために資格情報フローを利用する静的ページ向けです。このローカルプロバイダーは、バージョン0.9.0以降、リフレッシュトークンもサポートしています。詳しくはこちらをご覧ください。 ...

Nuxt3と@sidebase/nuxt-authを使って、Drupalの認証を行う

Nuxt3と@sidebase/nuxt-authを使って、Drupalの認証を行う

概要 Nuxt3と@sidebase/nuxt-authを使って、Drupalの認証を行う方法です。 背景 以下の記事で、GakuNin RDMの認証を行う方法を紹介しました。 また、以下の記事で、Next.jsからDrupalのOAuthを利用する方法を紹介しました。 これらを参考にして、Nuxt3からDrupalのOAuthを利用します。 方法 ソースコードは以下のリポジトリでご確認いただけます。 https://github.com/nakamura196/nuxt-rdm 具体的には、以下です。 https://github.com/nakamura196/nuxt-rdm/blob/main/server/api/auth/[…].ts { id: "drupal", name: "Drupal", type: "oauth", clientId: useRuntimeConfig().drupalClientId, clientSecret: useRuntimeConfig().drupalClientSecret, authorization: { url: process.env.DRUPAL_AUTH_URL, params: { scope: process.env.DRUPAL_SCOPE, response_type: "code", redirect_uri: `${ useRuntimeConfig().nextAuthUrl }/api/auth/callback/drupal`, }, }, token: { async request(context) { const body = new URLSearchParams({ client_id: useRuntimeConfig().drupalClientId, client_secret: useRuntimeConfig().drupalClientSecret, code: context.params.code || "", grant_type: "authorization_code", redirect_uri: `${ useRuntimeConfig().nextAuthUrl }/api/auth/callback/drupal`, }); const res = await fetch(process.env.DRUPAL_TOKEN_URL || "", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", }, body, }); const json = await res.json(); // Parse the response body once if (!res.ok) { throw new Error(`Token request failed: ${res.statusText}`); } return { tokens: json }; }, }, profile(profile) { return { id: profile.sub, // "sub" をユーザーの一意のIDとして利用 name: profile.name || profile.preferred_username || "Unknown User", // 名前の優先順位を設定 email: profile.email || "No Email Provided", // メールがない場合のフォールバック image: profile.profile || null, // プロファイルURLを画像として使用(必要に応じて調整) }; }, }, まとめ 間違っている点もあるかもしれませんが、参考になりましたら幸いです。 ...

Omeka S: Advanced Searchモジュールに対応したテーマを探す

Omeka S: Advanced Searchモジュールに対応したテーマを探す

概要 Omeka SのAdvanced Searchモジュールに対応したテーマを探す方法の一例について紹介します。 背景 Omeka SのAdvanced Searchモジュールを用いることで、以下の記事などで紹介しているように、Omeka Sの検索画面をカスタマイズすることができます。 特に、ファセットなどを追加できる点に利点があります。 一方、使用しているテーマがこのAdvanced Searchモジュールに対応していない場合、一部表示が崩れてしまうケースがあります。テーマ側でAdvanced Searchモジュールに対応しているかどうかを判断する方法の一例として、以下のように、テーマのフォルダの「view/common」の下に「advanced-search」の有無を確認する方法があります。 https://github.com/omeka-s-themes/freedom/tree/master/view/common/advanced-search この方法に基づいて、GitHubで公開されているOmeka Sのテーマのうち、Advanced Searchモジュールに対応しているものを探す方法を紹介します。 方法 以下の記事で紹介したサイトを使用します。 URLは以下です。 https://satoru196.notion.site/satoru196/6f898ed1352e4c9fa013eee635cbabf4?v=02cab757b6cf4df6bfbedfeb85eca0a5 特に、本記事の目的のため、「各テーマがAdvanced Searchモジュールに対応しているか」のフラグを追加しました。加えて、リポジトリのownerの情報も加え、各テーマが誰によって提供されているかを確認できるようにしました。 具体的には、以下の図に示すように、スター数で降順として、さらに「has_advanced_search」にチェックが入っているテーマのみに限定します。 この結果、「freedom」というテーマが「omeka-s-themes」というOmekaの公式Teamによって提供されており、相対的にスター数が多く、Advanced Searchモジュールにも対応していることがわかります。 https://github.com/omeka-s-themes/freedom まとめ Omeka Sのテーマの探し方の一例について紹介しました。Omeka Sの利用にあたり、参考になりましたら幸いです。

NextAuth.jsを使ってDrupalのOAuthを利用する

NextAuth.jsを使ってDrupalのOAuthを利用する

概要 NextAuth.jsを使ってDrupalのOAuthを利用する方法に関する備忘録です。 挙動 Next.jsで作成したアプリにアクセスして、「Sign in」ボタンを押します。 Drupalにログインしていない場合には、ログイン画面に遷移します。 ログイン済みの場合、「許可」するかのボタンが表示されるので、許可します。 ログイン情報が表示されます。 Drupal側の準備 モジュールのインストール 以下のモジュールをインストールします。 https://www.drupal.org/project/simple_oauth 本記事執筆時点の最新の以下をインストールしました。 composer require 'drupal/simple_oauth:^6.0@beta' トークンを暗号化するための鍵の生成 鍵のペアを生成し、セキュリティのためにドキュメントルートの外に保存します。 openssl genrsa -out private.key 2048 openssl rsa -in private.key -pubout > public.key 鍵のパスを設定 鍵のパスを管理画面で設定します: /admin/config/people/simple_oauth Amazon LightsailでDrupalを動作させている場合、以下のように、ユーザを変更する必要がありました。 sudo chown daemon:daemon private.key Clients /admin/config/services/consumerにアクセスします。 default_consumerがすでに作成されているため、こちらを編集します。 New Secretに値を入力します。 Grant typesでAuthorization Codeを選択します。 Redirect URIsに以下を入力します。 http://localhost:3000/api/auth/callback/drupal https://oauth.pstmn.io/v1/callback (Postmanでのチェック用) Access token expiration timeに値を入力します。 Postmanでの動作確認 以下のように入力します。Drupalがhttps://drupal.example.orgにセットアップされていると過程します。 認可 URL: https://drupal.example.org/oauth/authorize アクセストークン URL: https://drupal.example.org/oauth/token クライアント ID: Drupalで設定した値 クライアントシークレット: Drupalで設定した値 ...

AppSheetを使ってデジタルコレクションを構築する

AppSheetを使ってデジタルコレクションを構築する

概要 AppSheetを使ってデジタルコレクションを試作してみましたので、備忘録です。 対象データ 以下の記事と同じデータを利用しています。 具体的には、『東京帝國大學本部構内及農學部建物鳥瞰圖』(東京大学農学生命科学図書館所蔵)をサンプルデータとして使用します。 https://iiif.dl.itc.u-tokyo.ac.jp/repo/s/agriculture/document/187cc82d-11e6-9912-9dd4-b4cca9b10970 成果物 以下のURLからアクセスいただけます。 https://www.appsheet.com/start/092a3ff4-1074-4e27-bbd1-f4820da77511 画像の一覧機能や、 地図へのマッピング機能、 簡単な集計機能を提供します。 作成方法 アプリの初期設定 アプリを作成します。 データソースとして、Google Sheetsを選択します。ちなみに、「AppSheet Database」を使用した場合、Free Planではデプロイできませんでした。 結果、デフォルトで地図表示、およびデータの一覧を表示するページ(View)を作成してくれました。 データ 「データ」タブに移動して、設定を行います。 例えば、使用しない項目には、「SHOW?」列のチェックを外します。また、「KEY?」と「LABEL?」に対して、「identifier」と「title」を割り当てました。 次に、「DISPLAY NAME」で、アプリ上で表示する名称を入力します。 さらに、検索に使用したい項目にのみ、「SEARCH?」列にチェックを入れます。 加えて、今回はデータの閲覧のみを行うため、「Read-Only」の設定を行います。 Views(マップ) Views(マップ)はデフォルトのまま使用しました。 Views(main) ここでは、View nameを「Browse」に変更して、View typeとして「card」を選択して、Positionには「next」を与えました。Positionについては、メニューにおいて「Browse」が「Map」の左にくるようになります。 さらに、レイアウトについて、それぞれの要素に割り当てるカラムを設定します。例えば以下では、画像を表示する列として、「_image_url」を指定しています。 結果、一覧画面が以下のように表示されます。 Views(chart) 以下のように、新規にViewを追加します。 そして、「Chart columns」に、ここではカテゴリが格納されたカラムを設定しました。結果、画面右部のように表示が変わります。 その他 その他、アプリの見た目を変更します。 設定 > Theme & Brand から、例えば、ヘッダーにロゴなどが表示されるように変更します。 また、今回の初期設定では「Map」Viewがはじめに開いてしまうので、「Browse」が表示されるように、「Views」の設定を変更します。 Preview ここまでのところでプロトタイプはほぼ完成です。Previewモードで確認することができます。デフォルトで、各レコードの詳細画面も表示されます。 ...

Azure Logic Appsを試す

Azure Logic Appsを試す

概要 ノーコードまたはローコード開発の調査を目的として、Azure Logic Appsを試してみましたので備忘録です。 成果物 以下は、ロジックアプリデザイナーの画面です。HTTPリクエストを受信し、Cosmos DBにデータを保存し、成功時にメールを送信するワークフローを作成します。 Azure Cosmos DBの作成 「アカウント名」以外はデフォルトにしました。「my-first-azure-cosmos-db-account」という名前で作成しました。 「Items」コンテナを作成します。 「データエクスプローラ」を使って、データベース「ToDoList」の下に、コレクション「Items」が作成されていることを確認できます。 ロジック アプリの作成 ロジック アプリの作成を行います。 「my-first-logic-app」を作成しました。 「ロジックアプリデザイナー」に移動します。 HTTPリクエスト まず、「Request」を選びます。 そして、以下を入力します。今回は単純に「url」と「code」という項目を管理します。 { "type": "object", "properties": { "url": { "type": "string" }, "code": { "type": "string" } }, "required": [ "url", "code" ] } Cosmos DBの追加 まずコネクションを作成します。 入力すべき値は、「Azure Cosmos DB アカウント」ページの「設定 > キー」から確認することができました。 次に、パラメータを入力します。先ほど作成したコレクション「Items」を選択して、「ドキュメント」に以下を入力します。 { "id": "@{guid()}", "url": "@{triggerBody()?['url']}", "code": "@{triggerBody()?['code']}" } idとしてUUIDを入力し、フォームから受け取ったurlとcodeをDBに登録します。 メールの送信 最後に、メールの送信設定です。今回は「Office 365 Outlook」の「メールの送信(V2)」を選択しました。 ...

Azure OpenAI Assistants APIを用いたアプリをGradioとNext.jsで作成する

Azure OpenAI Assistants APIを用いたアプリをGradioとNext.jsで作成する

概要 Azure OpenAI Assistants APIを用いたアプリをGradioとNext.jsで作成したので、備忘録です。 対象データ Zennで公開している記事を対象にしました。まず以下により、一括ダウンロードしました。 import requests from bs4 import BeautifulSoup import os from tqdm import tqdm page = 1 urls = [] while 1: url = f"https://zenn.dev/api/articles?username=nakamura196&page={page}" response = requests.get(url) data = response.json() articles = data['articles'] if len(articles) == 0: break for article in articles: urls.append("https://zenn.dev" + article['path']) page += 1 for url in tqdm(urls): text_opath = f"data/text/{url.split('/')[-1]}.txt" if os.path.exists(text_opath): continue response = requests.get(url) soup = BeautifulSoup(response.text, "html.parser") html = soup.find(class_="znc") txt = html.get_text() os.makedirs(os.path.dirname(text_opath), exist_ok=True) with open(text_opath, "w") as f: f.write(txt) ベクトルストアへの登録 以下のようなコードにより、データファイルをアップロードします。 import os from dotenv import load_dotenv from openai import AzureOpenAI from glob import glob from tqdm import tqdm load_dotenv(override=True) client = AzureOpenAI( azure_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT_ZENN"), api_key=os.getenv("AZURE_OPENAI_API_KEY_ZENN"), api_version="2024-05-01-preview" ) # ベクトルストアの作成または取得 is_create_vector_store = True vector_store_name = "Vector Store" if is_create_vector_store: vector_store = client.beta.vector_stores.create(name=vector_store_name) # Create a vector store caled "Financial Statements" vector_stores = client.beta.vector_stores.list() for vector_store in vector_stores: if vector_store.name == vector_store_name: vector_store_id = vector_store.id break # 登録済みデータファイルの取得 response = client.files.list(purpose="assistants") items = response.data filenames = [] for item in items: filename = item.filename filenames.append(filename) filenames.sort() # アップロード ## 定数設定 BATCH_SIZE = 100 vector_store_id = "vs_UELnIBkcROD3o4XKX2CcpVjo" ## ファイル一覧取得とソート files = glob("./data/text/*.txt") files.sort() ## アップロード済みファイルを確認済みと仮定 file_streams = [] for file in tqdm(files): filename = os.path.basename(file) if filename in filenames: # アップロード済みのファイルをスキップ continue ## ファイルをストリームとして開く file_streams.append(open(file, "rb")) ## バッチサイズに達したらアップロード処理 if len(file_streams) == BATCH_SIZE: try: client.beta.vector_stores.file_batches.upload_and_poll( vector_store_id=vector_store_id, files=file_streams ) except Exception as e: print(f"Error processing batch: {e}") finally: file_streams = [] # ストリームリセット ## 残りのファイルを処理 if file_streams: try: client.beta.vector_stores.file_batches.upload_and_poll( vector_store_id=vector_store_id, files=file_streams ) except Exception as e: print(f"Error processing remaining files: {e}") アシスタント プレイグラウンド 「アシスタント プレイグラウンド」を用いて、挙動を確認します。 ...

Kompakkt Standalone Viewerを試す

Kompakkt Standalone Viewerを試す

概要 Kompakkt Standalone Viewerを試す機会がありましたので、備忘録です。 以下のように説明されています。 This repository hosts a JavaScript file which can be included on any website to use the Kompakkt Viewer without needing to use the Kompakkt Repository or the Kompakkt Server. (機械翻訳)このリポジトリには、Kompakkt Viewerを使用するためのJavaScriptファイルが含まれており、このファイルをウェブサイトに組み込むだけで、Kompakkt RepositoryやKompakkt Serverを使用せずにビューアを利用することができます。 リポジトリ 以下で公開されています。 https://github.com/Kompakkt/StandaloneViewer またGitHub Pagesでビューアにアクセスすることができます。 https://kompakkt.github.io/StandaloneViewer/ 表示例 以下のように、3Dモデルとアノテーションを表示することができました。 https://kompakkt.github.io/StandaloneViewer/?state=eyJyZXNvdXJjZSI6Imh0dHBzOi8vc3VraWxhbS5hd3MubGRhcy5qcC9maWxlcy9vcmlnaW5hbC8yNTNlZmRmMzQ0Nzg0NTk5NTRhZTA0ZjZiM2JlZmE1ZjM4MjJlZDU5LmdsYiIsImFubm90YXRpb25zIjpbeyJfaWQiOiI2NzcwYTE1NDRjZTFmZDAyMzQ4OGVkODAiLCJib2R5Ijp7InR5cGUiOiJhbm5vdGF0aW9uIiwiY29udGVudCI6eyJ0eXBlIjoidGV4dCIsInRpdGxlIjoibXkgdGl0bGUiLCJkZXNjcmlwdGlvbiI6Im15IGRlc2NyaXB0aW9uIiwicmVsYXRlZFBlcnNwZWN0aXZlIjp7ImNhbWVyYVR5cGUiOiJhcmNSb3RhdGVDYW0iLCJwb3NpdGlvbiI6eyJ4IjoyLjI5NzE5OTgyNzA3MzIxLCJ5IjoxLjA0NjMzMzI0ODg4Njk0LCJ6IjoxLjYzNjAwODMyNDk5NDF9LCJ0YXJnZXQiOnsieCI6MS4wMTMxMDUwMzQ4MjgxOSwieSI6MS4xNzkwMzM5OTQ2NzQ2OCwieiI6MS4wMTQ0NDIwMjY2MTUxNH0sInByZXZpZXciOiJwcmV2aWV3cy9hbm5vdGF0aW9uLzY3NzBhMTU0NGNlMWZkMDIzNDg4ZWQ4MC5wbmcifX19LCJjcmVhdGVkIjoiMjAyNC0xMi0yOVQwMTowOTo0MC4yODZaIiwiY3JlYXRvciI6eyJ0eXBlIjoicGVyc29uIiwibmFtZSI6IlNhdG9ydSBOYWthbXVyYSIsIl9pZCI6IjY3NWE5Y2U0N2QxZmIyNDUzZjBmYjNjMSJ9LCJnZW5lcmF0ZWQiOiIyMDI0LTEyLTI5VDAxOjA5OjQyLjk3NFoiLCJnZW5lcmF0b3IiOnsidHlwZSI6InNvZnR3YXJlIiwibmFtZSI6IktvbXBha2t0IiwiX2lkIjoiNjc1YTljZTQ3ZDFmYjI0NTNmMGZiM2MxIiwiaG9tZXBhZ2UiOiJodHRwczovL2dpdGh1Yi5jb20vS29tcGFra3QvS29tcGFra3QifSwiaWRlbnRpZmllciI6IjY3NzBhMTU0NGNlMWZkMDIzNDg4ZWQ4MCIsImxhc3RNb2RpZmljYXRpb25EYXRlIjoiMjAyNC0xMi0yOVQwMToxMToyNC4zNjBaIiwibGFzdE1vZGlmaWVkQnkiOnsiX2lkIjoiNjc1YTljZTQ3ZDFmYjI0NTNmMGZiM2MxIiwibmFtZSI6IlNhdG9ydSBOYWthbXVyYSIsInR5cGUiOiJwZXJzb24ifSwibW90aXZhdGlvbiI6ImRlZmF1bHRNb3RpdmF0aW9uIiwicmFua2luZyI6MSwidGFyZ2V0Ijp7InNvdXJjZSI6eyJyZWxhdGVkRW50aXR5IjoiNjc3MDllODYwY2I4ZDA2NGZhMDc1NWI3IiwicmVsYXRlZENvbXBpbGF0aW9uIjoiIn0sInNlbGVjdG9yIjp7InJlZmVyZW5jZVBvaW50Ijp7Il9pc0RpcnR5Ijp0cnVlLCJfeCI6MC40MzI0MjIyMjQwNDc5MDksIl95IjoxLjY3NDQzMTE3MTYxNjA1LCJfeiI6MS42NTQ2MTgxODc1NDE1OH0sInJlZmVyZW5jZU5vcm1hbCI6bnVsbH19LCJ2YWxpZGF0ZWQiOnRydWV9XX0%253D ただし、アノテーションに日本語が含まれているとエラーが発生してしまいました。 また、アノテーションが3Dモデル上に表示されないなど、使用方法に誤りが含まれるようでした。 補足 入力するアノテーションのフォーマットとして、Kompakktで付与したアノテーションをエクスポートしたものが利用できるようでした。 例えば、以下です。 [ { "_id": "6770a1544ce1fd023488ed80", "body": { "type": "annotation", "content": { "type": "text", "title": "aaa", "description": "bbb", "relatedPerspective": { "cameraType": "arcRotateCam", "position": { "x": 2.29719982707321, "y": 1.04633324888694, "z": 1.6360083249941 }, "target": { "x": 1.01310503482819, "y": 1.17903399467468, "z": 1.01444202661514 }, "preview": "previews/annotation/6770a1544ce1fd023488ed80.png" } } }, "created": "2024-12-29T01:09:40.286Z", "creator": { "type": "person", "name": "Satoru Nakamura", "_id": "675a9ce47d1fb2453f0fb3c1" }, "generated": "2024-12-29T01:09:42.974Z", "generator": { "type": "software", "name": "Kompakkt", "_id": "675a9ce47d1fb2453f0fb3c1", "homepage": "https://github.com/Kompakkt/Kompakkt" }, "identifier": "6770a1544ce1fd023488ed80", "lastModificationDate": "2025-01-04T22:47:44.949Z", "lastModifiedBy": { "_id": "675a9ce47d1fb2453f0fb3c1", "name": "Satoru Nakamura", "type": "person" }, "motivation": "defaultMotivation", "ranking": 1, "target": { "source": { "relatedEntity": "67709e860cb8d064fa0755b7", "relatedCompilation": "" }, "selector": { "referencePoint": { "_isDirty": true, "_x": 0.432422224047909, "_y": 1.67443117161605, "_z": 1.65461818754158 }, "referenceNormal": null } }, "validated": true } ] ただし、Kompakktでも3Dモデル上にアノテーションが表示されず、使用方法のどこかに誤りがありそうです。 ...

Pythonを使ってOmeka Sにメディアをアップロードする方法

Pythonを使ってOmeka Sにメディアをアップロードする方法

概要 Pythonを使ってOmeka Sにメディアをアップロードする方法の備忘録です。 準備 環境変数を用意します。 OMEKA_S_BASE_URL=https://dev.omeka.org/omeka-s-sandbox # 例 OMEKA_S_KEY_IDENTITY= OMEKA_S_KEY_CREDENTIAL= 初期化します。 import requests from dotenv import load_dotenv import os def __init__(self): load_dotenv(verbose=True, override=True) OMEKA_S_BASE_URL = os.environ.get("OMEKA_S_BASE_URL") self.omeka_s_base_url = OMEKA_S_BASE_URL self.items_url = f"{OMEKA_S_BASE_URL}/api/items" self.media_url = f"{OMEKA_S_BASE_URL}/api/media" self.params = { "key_identity": os.environ.get("OMEKA_S_KEY_IDENTITY"), "key_credential": os.environ.get("OMEKA_S_KEY_CREDENTIAL") } ローカルファイルをアップロードする def upload_media(self, path, item_id): files = {} payload = {} file_data = { 'o:ingester': 'upload', 'file_index': '0', 'o:source': path.name, 'o:item': {'o:id': item_id} } payload.update(file_data) params = self.params files = [ ('data', (None, json.dumps(payload), 'application/json')), ('file[0]', (path.name, open(path, 'rb'), 'image')) ] media_response = requests.post( self.media_url, params=params, files=files ) # レスポンスを確認 if media_response.status_code == 200: return media_response.json()["o:id"] else: return None IIIF画像をアップロードする 以下のようなIIIF画像のURLを指定して登録します。 https://dl.ndl.go.jp/api/iiif/1288277/R0000030/info.json def upload_media(self, iiif_url, item_id): payload = { 'o:ingester': 'iiif', 'file_index': '0', 'o:source': iiif_url, 'o:item': {'o:id': item_id}, } media_response = requests.post( self.media_url, params=self.params, headers={'Content-Type': 'application/json'}, data=json.dumps(payload) ) # レスポンスを確認 if media_response.status_code == 200: return media_response.json()["o:id"] else: return None まとめ Omeka Sへの画像登録にあたり、参考になりましたら幸いです。 ...

Sketchfabのアノテーションを試す

Sketchfabのアノテーションを試す

概要 Sketchfabのアノテーションを試してみましたので、備忘録です。 最終的に、以下のようなビューアを作成しました。 https://nakamura196.github.io/SketchfabAnnotationViewer/ https://youtu.be/iEe6TbI3X70 使用データ 「菊池市/デジタルアーカイブ」の「石淵家地球儀」を対象とします。 https://adeac.jp/kikuchi-city/catalog/e0001 使用例 まずSketchfabに3Dデータをアップロードしました。 https://skfb.ly/pt8oU そしてアノテーションを付与しました。結果、以下のようなページが用意されました。 APIを利用する 以下のリポジトリも参考にしてください。 https://github.com/nakamura196/SketchfabAnnotationViewer 以下のようなスクリプトにより、アノテーションの一覧取得や、初期表示に使用するアノテーションの指定、選択したアノテーションへのフォーカスなどを行うことができました。 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Sketchfab Annotation Viewer</title> <script src="https://cdn.tailwindcss.com"></script> </head> <body class="bg-gray-100 font-sans"> <!-- メインコンテナ --> <div class="max-w-4xl mx-auto py-10 px-4"> <!-- ヘッダー --> <h1 class="text-2xl font-bold text-gray-800 text-center mb-6"> Sketchfab Annotation Viewer </h1> <!-- 3Dビューア --> <div class="mb-6"> <iframe id="api-frame" width="100%" height="480" class="rounded shadow-md border border-gray-300" src="" allowfullscreen ></iframe> </div> <!-- アノテーションリスト --> <h2 class="text-xl font-semibold text-gray-700 mb-4">Annotations</h2> <ul id="annotation-list" class="space-y-2"> <!-- アノテーション項目はJavaScriptで追加 --> </ul> </div> <script src="https://static.sketchfab.com/api/sketchfab-viewer-1.12.1.js"></script> <script src="script.js"></script> </body> </html> // Sketchfab Viewerを埋め込むためのiframeを取得 const iframe = document.getElementById('api-frame'); const client = new Sketchfab(iframe); const urlParams = new URLSearchParams(window.location.search); // SketchfabモデルIDを指定 const modelId = urlParams.get('id') || '02add905e79c446994f971cbcf443815'; // 'id'パラメータを取得 const pos = parseInt(urlParams.get('pos'), 10) || 0; // APIのオプションを指定してモデルをロード client.init(modelId, { success: function (api) { api.start(); api.addEventListener('viewerready', function () { setupAnnotations(api); focusAnnotation(api, pos); // 最初のアノテーションにフォーカス }); }, error: function () { console.error('Sketchfab Viewer failed to load'); }, }); function setupAnnotations(api) { api.getAnnotationList(function (err, annotations) { if (err) { console.error('Failed to fetch annotations'); return; } // アノテーション一覧をHTMLに追加 const annotationListContainer = document.getElementById('annotation-list'); annotations.forEach((annotation, index) => { const annotationItem = document.createElement('li'); annotationItem.textContent = annotation.name; // アノテーションタイトル annotationItem.addEventListener('click', () => { focusAnnotation(api, index); // クリック時にフォーカス }); annotationListContainer.appendChild(annotationItem); }); }); } function focusAnnotation(api, annotationIndex) { api.gotoAnnotation(annotationIndex, { preventCameraAnimation: false, // アニメーションを許可 }); // api.showAnnotation(annotationIndex); // アノテーションを表示 // api.showAnnotationTooltip(annotationIndex); // アノテーションツールチップを表示 } まとめ 3Dデータへのアノテーションの応用にあたり、参考になりましたら幸いです。 ...

objファイルをgltf, glbファイルに変換する

objファイルをgltf, glbファイルに変換する

概要 objファイルをgltf, glbファイルに変換する方法の備忘録です。 対象データ 「菊池市/デジタルアーカイブ」の「石淵家地球儀」を対象とします。 https://adeac.jp/kikuchi-city/catalog/e0001 objファイルは以下のURLからアクセスできます。 https://adeac.jp/viewitem/kikuchi-city/viewer/3d/dc-e0097/models/Kikuchi_Globe_180820.obj 対象データのダウンロード ライブラリをダウンロードします。 npm i axios 以下のファイルを用意します。 const axios = require('axios'); const fs = require('fs'); const path = require('path'); // 指定されたURLからファイルをダウンロードする関数 async function downloadFile(url, outputPath) { const writer = fs.createWriteStream(outputPath); const response = await axios({ url, method: 'GET', responseType: 'stream', }); response.data.pipe(writer); return new Promise((resolve, reject) => { writer.on('finish', resolve); writer.on('error', reject); }); } // .obj ファイルをロードし、関連ファイルをダウンロードする async function processObjFile(objUrl, outputDir) { try { // .obj ファイルをダウンロードして内容を取得 const objResponse = await axios.get(objUrl); const objContent = objResponse.data; // .obj ファイルを保存 const objFileName = path.basename(objUrl); const objFilePath = path.join(outputDir, objFileName); fs.writeFileSync(objFilePath, objContent); console.log(`Downloaded OBJ file: ${objFilePath}`); // .mtl ファイルのパスを検索 const mtlMatch = objContent.match(/^mtllib\s+(.+)$/m); if (mtlMatch) { const mtlFileName = mtlMatch[1]; const mtlUrl = new URL(mtlFileName, objUrl).href; const mtlFilePath = path.join(outputDir, mtlFileName); // .mtl ファイルをダウンロード await downloadFile(mtlUrl, mtlFilePath); console.log(`Downloaded MTL file: ${mtlFilePath}`); // .mtl ファイルの内容を取得して関連ファイルを探す const mtlContent = fs.readFileSync(mtlFilePath, 'utf-8'); const textureMatches = [...mtlContent.matchAll(/^map_Kd\s+(.+)$/gm)]; for (const match of textureMatches) { const textureFileName = match[1]; const textureUrl = new URL(textureFileName, objUrl).href; const textureFilePath = path.join(outputDir, path.basename(textureFileName)); // テクスチャ画像をダウンロード await downloadFile(textureUrl, textureFilePath); console.log(`Downloaded texture file: ${textureFilePath}`); } } else { console.log('No MTL file referenced in the OBJ file.'); } } catch (error) { console.error(`Error processing OBJ file: ${error.message}`); } } // 使用例 const objUrl = 'https://adeac.jp/viewitem/kikuchi-city/viewer/3d/dc-e0097/models/Kikuchi_Globe_180820.obj'; const outputDir = './downloads'; if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } processObjFile(objUrl, outputDir); 実行します。 ...

aleph-r3fを試す

aleph-r3fを試す

概要 以下の記事で、Aleph 3D viewerを紹介しました。 その後調べた結果、以下のリポジトリの存在も知りました。 https://github.com/aleph-viewer/aleph-r3f 以下のように説明されており、react-three-fiberとshadcn/uiを使用している点に違いがあるようでした。 Aleph is a 3D object viewer and annotation/measurement tool built with react-three-fiber and shadcn/ui 以下のように、アノテーション付与機能なども改良されているようでした。 今回の記事でも、菊池市デジタルアーカイブで公開されている「石淵家地球儀」の3Dデータを使用します。 https://adeac.jp/kikuchi-city/catalog/e0001 使い方 以下で閲覧いただけます。 https://iiif-aleph-r3f.vercel.app/ アノテーションタブで、アノテーションの付与を行うことができました。 アノテーションデータのインポート/エクスポートを行うことができ、JSON形式でのエクスポート結果は以下でした。 [ { "position": { "x": -0.06690392681702004, "y": 0.6256817352784154, "z": -0.7424544387001097 }, "normal": { "x": -0.11627753958254597, "y": 0.6430031011979032, "z": -0.7569851687044529 }, "cameraPosition": { "x": -0.15922188799592055, "y": 1.1767071158114843, "z": -1.4378842144444104 }, "cameraTarget": { "x": -0.0023649930953979492, "y": -0.0009789466857910165, "z": -0.011684000492095947 }, "rotation": { "isEuler": true, "_x": 0, "_y": 0, "_z": 0, "_order": "XYZ" }, "label": "大西洋", "description": "初めてのアノテーション" } ] カスタマイズ 「石淵家地球儀」を表示するにあたり、以下のようにソースコードを編集する必要がありました。 import './App.css'; import { useEffect, useRef } from 'react'; ... const [{ src }, _setLevaControls] = useControls(() => ({ src: { options: { // 'Measurement Cube': { // url: 'https://cdn.glitch.global/afd88411-0206-477e-b65f-3d1f201de994/measurement_cube.glb?v=1710500461208', // label: 'Measurement Cube', // }, 石淵家地球儀: 'https://sukilam.aws.ldas.jp/files/original/253efdf34478459954ae04f6b3befa5f3822ed59.glb', 'Flight Helmet': 'https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Assets/main/Models/FlightHelmet/glTF/FlightHelmet.gltf', ... また、Vercelへのデプロイにあたり、以下のように、tailwind.config.jsを修正する必要がありました。 ...

Aleph 3D viewerを試す

Aleph 3D viewerを試す

概要 3D object viewerの一つであるAlephを試してみましたので、備忘録です。 https://github.com/aleph-viewer/aleph 菊池市デジタルアーカイブで公開されている「石淵家地球儀」の3Dデータを使用しています。 https://adeac.jp/kikuchi-city/catalog/e0001 背景 IIIF対応の3Dビューアを調査する過程で、以下の記事を見つけました。 https://pro.europeana.eu/post/iiif-for-3d-making-web-interoperability-multi-dimensional こちらで紹介されているビューアの一つとして、Alephを知りました。 使い方 GitHubリポジトリをForkして、Vercelにデプロイしました。 https://aleph-coral.vercel.app/ 初期表示は以下です。 画面左部の入力フォームにあるglbファイルへのURLを変更することで、指定した3Dモデルが表示されました。 まとめ 3Dビューアの調査にあたり、参考になりましたら幸いです。