Romaを使ってタグの属性に使用可能な値を限定する

概要 Romaを使ってタグの属性に使用可能な値を限定する方法に関する備忘録です。 背景 以下の記事で、タグに使用可能な属性を限定する方法を記載しました。 例えば、persNameタグには、key属性とtype属性のみを使用可能にする、といった具合です。 本記事では、さらに特定の属性で使用可能な値を限定します。例えば、type属性には、「右傍注」または「左傍注」のいずれかを設定する、といった具合です。 Romaでの設定 以下の記事を参考に、タグの属性の設定を行います。 ここでは、persNameタグにtype属性を設定済みとします。そして、以下のように、鉛筆アイコンをクリックします。 以下のように、属性に関する情報を編集するためのページに遷移します。ここで、「値」という項目において、「右傍注」「左傍注」といった値を登録します。合わせて、必要に応じて「説明」文も追加します。 Oxygen XML Editorでの表示例 rngファイルとしてダウンロードし、それをTEI/XMLからロードすることにより、Oxygen XML Editorでは以下のように表示されました。 LEAF Writerでの表示例 LEAF Writerでは、以下のように、セレクトボックスで選択肢が提示されました。 まとめ TEI/XMLの導入あたり、参考になりましたら幸いです。

2024年10月28日 · 1 分 · Nakamura

Romaを使ってプロジェクトに応じたタグに使用する属性を限定する

概要 Romaを使ってプロジェクトに応じたタグに使用する属性を限定する方法に関する備忘録です。 背景 以下の記事で、Romaを使ってプロジェクトに応じたタグを限定する方法を記載しました。 今回はこの延長で、各タグで使用する属性のカスタマイズを行います。 ユースケース ここでは、一例として、persNameで使用可能な属性を限定してみます。 デフォルト(tei_all.rng)をOxygen XML Editorで用いた際、以下のように、persNameタグで使用可能な属性として、多くの選択肢が提示されていることがわかります。 一方、本記事で説明するカスタマイズしたrngファイルを使用した場合、以下のように、5つの属性のみが利用可能となっていることがわかります。 このようにプロジェクト毎に使用可能なタグや属性を限定することで、入力者の負担軽減や、Validationの効率化が期待できます。 手順 以下の記事を参考に、Romaで新規にODDファイルを作成するか、既存のODDファイルを登録した状態から開始します。 そして、今回対象とするpersNameにチェックが入っていることを確認します。 次に、上記のpersNameのリンクをクリックすると、以下の画面に遷移します。 そして、属性をクリックします。このページにおいて、使用する属性を限定することができます。 以下では、少しわかりにくいですが、key属性は使用するものとして残しており、xml:lang属性は使用しないものとして除外している例です。 その他、新規の属性の作成や、 既存のものからインポート(用語が正しいか自身がありません)することもできました。 このカスタマイズ内容を保存できるように、ダウンロード > 「ODDとしてカスタマイズ」により、oddファイルをダウンロードしておきます。また、「RELAX NGスキーマ」などを選択して、実際にTEI/XMLで使用するファイルをダウンロードします。 詳細は以下を参考にしてください。 参考:説明文のカスタマイズ Roma RELAX NG スキーマ <define name="tei_persName"> <element name="persName"> <a:documentation xmlns:a="http://relaxng.org/ns/compatibility/annotations/1.0">(personal name) 日本語によるカスタマイズです。 [14.2.1. Personal Names]</a:documentation> <ref name="tei_macro.phraseSeq"/> <pattern xmlns="http://purl.oclc.org/dsdl/schematron" xmlns:rng="http://relaxng.org/ns/structure/1.0" id="tei_hi_dns-persName-calendar-check-persName-constraint-rule-23"> <sch:rule xmlns:sch="http://purl.oclc.org/dsdl/schematron" xmlns:xi="http://www.w3.org/2001/XInclude" xmlns="http://www.tei-c.org/ns/1.0" context="tei:*[@calendar]"> <sch:assert test="string-length( normalize-space(.) ) gt 0"> @calendar indicates one or more systems or calendars to which the date represented by the content of this element belongs, but this <sch:name/> element has no textual content.</sch:assert> </sch:rule> </pattern> <ref name="tei_att.canonical.attribute.key"/> <ref name="tei_att.global.attribute.xmlid"/> <ref name="tei_att.global.attribute.n"/> <ref name="tei_att.global.linking.attribute.corresp"/> <ref name="tei_att.global.analytic.attribute.ana"/> <empty/> </element> </define> LEAF Writer ...

2024年10月28日 · 1 分 · Nakamura

GakuNin RDM APIをつかってみる

概要 GakuNin RDMでは、以下でAPIが公開されています。このAPIの使用例について備忘録です。 https://api.rdm.nii.ac.jp/v2/ 参考 GakuNin RDMはOSF(Open Science Framework)をベースに構築されており、APIに関するドキュメントは以下で確認することができます。OpenAPIに準拠しています。 https://developer.osf.io/ PATの取得 PAT(パーソナルアクセストークン)を取得します。 ログイン後、以下のURLから作成することができます。 https://rdm.nii.ac.jp/settings/tokens/ 利用 以下のようなスクリプトにより、プログラムからもアクセスすることができます。 access_token=xxx import requests import os from dotenv import load_dotenv load_dotenv(verbose=True) load_dotenv("./env") access_token = os.environ.get("access_token") # アップロードURLにクエリパラメータを追加 url = f'https://api.rdm.nii.ac.jp/v2/nodes/' # ファイルを開き、PUTリクエストでアップロード # with open(file_path, 'rb') as file: response = requests.get( url, headers={ 'Authorization': f'Bearer {access_token}' } ) response.json() 参考までに、Authorizationヘッダーが未指定の場合、以下の結果が返却されました。 {'data': [], 'links': {'first': None, 'last': None, 'prev': None, 'next': None, 'meta': {'total': 0, 'per_page': 10}}, 'meta': {'version': '2.0'}} ブラウザで確認 APIの出力結果はブラウザでも確認することができます。 https://api.rdm.nii.ac.jp/v2/ 以下のように、Django REST frameworkで作成されていることがわかります。 また、おそらく「Django REST framework JSON:API」を使用し、JSON:APIが採用されていることがわかります。 まとめ GakuNin RDMおよびOSF(Open Science Framework)のAPIについて、参考になりましたら幸いです。 ...

2024年10月26日 · 1 分 · Nakamura

GakuNin RDMとZoteroを接続する

概要 GakuNin RDMとZoteroを接続する機会がありましたので、備忘録です。 以下のように、指定したZoteroのコレクションおよびフォルダを連携することができます。 方法 GakuNin RDMの/addons/において、Zoteroの「プロフィールからアカウントをインポート」リンクをクリックして、プロジェクトとZoteroアカウントを接続します。 その後、まずライブラリに設定を行います。 次にフォルダを指定します。 上記において、Changeや変更といったボタンを押して、それぞれ設定する必要がありました。 まとめ GakuNin RDMとZoteroの連携にあたり、参考になりましたら幸いです。

2024年10月25日 · 1 分 · Nakamura

Archivematicaにmdx.jpのオブジェクトストレージを追加する

概要 Archivematicaにmdx.jpのオブジェクトストレージを追加する機会がありましたので、備忘録です。 背景 以下の記事で、Amazon S3をArchivematicaの処理対象およびAIPの保存先に設定する方法を記載しました。 今回は、この手順をベースとしつつ、mdx.jpのオブジェクトストレージを接続してみます。 設定方法 以下のように設定します。 S3 Endpoint URLには、https://s3ds.mdx.jpを設定します。 Access Key ID to authenticateとSecret Access Key to authenticate withには、以下で得られるアクセスキーと秘密鍵を使用します。 結果 結果、以下のように、mdx.jpのオブジェクトストレージを入出力ストレージとして利用できるようになりました。これにより、AIPやDIPをmdx.jpのオブジェクトストレージに保存することができます。 補足 以下の記事で記載した方法を参考に、GakuNin RDMとmdx.jpのオブジェクトストレージを接続することができます。 これにより、GakuNin RDM上でAIPやDIPの確認が可能となります。これにより、GakuNin RDMのBinderHubを用いたAIPの分析や可視化が可能となります。 可視化例として、ArchivematicaのMETSファイルを人間に優しい方法で探索可能とするMETSFlaskの応用などが考えられます。 まとめ Archivematica, GakuNin RDM, mdx.jpなどの連携にあたり、参考になりましたら幸いです。

2024年10月25日 · 1 分 · Nakamura

ShExCとShExJの違い

概要 ShExC(ShEx Compact Syntax)とShExJ(ShEx JSON Syntax)の違いについて、ChatGPTによる回答です。誤っている点もあるかもしれませんが、参考になりましたら幸いです。 回答 ShExC(ShEx Compact Syntax)とShExJ(ShEx JSON Syntax)は、どちらもShEx(Shape Expressions)スキーマの表現形式ですが、表記形式や用途が異なります。以下にその違いを説明します。 1. 表記形式 ShExC(ShEx Compact Syntax) : テキストベース のシンタックスです。 人間が読み書きしやすいように設計されており、シンプルかつ簡潔にRDFデータの構造や制約を記述することができます。 主に手動で記述 したり、コード内に直接埋め込む ことを想定して設計されています。 例 : <PersonShape> { foaf:name xsd:string ; foaf:age xsd:integer ? } * これは「Person」という形状が、`foaf:name` という必須の文字列プロパティと、`foaf:age` というオプションの整数プロパティを持つことを示しています。 ShExJ(ShEx JSON Syntax) : JSONベース のシンタックスです。 機械可読性を重視した形式で、ShExスキーマをプログラムやAPIで扱うために設計されています。 ツールやライブラリで処理しやすい ため、特にプログラム間でのデータ交換やAPIレスポンスで使用されることが多いです。 例 : { "type": "Schema", "shapes": [ { "id": "PersonShape", "type": "Shape", "expression": { "type": "EachOf", "expressions": [ { "type": "TripleConstraint", "predicate": "foaf:name", "valueExpr": { "type": "NodeConstraint", "datatype": "xsd:string" } }, { "type": "TripleConstraint", "predicate": "foaf:age", "valueExpr": { "type": "NodeConstraint", "datatype": "xsd:integer" }, "min": 0, "max": 1 } ] } } ] } 2. 用途 ShExC : ...

2024年10月25日 · 1 分 · Nakamura

ShExとSHACLの違い

概要 ShEx(Shape Expressions)Schema と SHACL(Shapes Constraint Language)の違いに関するChatGPTによる回答です。間違っている点もあるかもしれませんが、参考になりましたら幸いです。 回答 ShEx(Shape Expressions)Schema と SHACL(Shapes Constraint Language)は、どちらもRDFデータに対する検証 や制約 を定義するための言語です。両者は同じ目的を持ちながらも、構文 やアプローチ に違いがあります。以下にその違いを説明します。 1. 目的と用途 ShEx(Shape Expressions) : ShExは、RDFデータが特定の形状(Shape)に従っているかどうかを 簡潔に定義し、検証するための言語です。 主に、柔軟で簡単なスキーマ定義 を目的として設計されており、特定の形状にデータが適合するかどうかの判定がしやすいです。 スキーマ検証 が主な用途で、特定のシェイプにデータが従っているかどうかをすばやく確認できます。 SHACL(Shapes Constraint Language) : SHACLは、RDFデータに対して複雑な制約 やルール を定義するために設計されたW3C標準の言語です。 より柔軟で強力な検証機能を持ち、スキーマ定義だけでなく、制約の論理的な定義 や高次の推論 を行うことができます。 例えば、条件付き制約や、プロパティの型、データの依存関係など、より複雑なルールを記述できます。 2. 構文と表現力 ShEx : シンプルでコンパクトな構文 を持ち、RDFデータの形状を定義する際に、簡単に読み書きできるよう設計されています。 直感的で短い記述 でシェイプを定義できるため、学習コストが低く、データ検証を行う際に適しています。 例(ShExC形式): <PersonShape> { foaf:name xsd:string ; foaf:age xsd:integer ? } * これは、「`PersonShape`は、`foaf:name`として文字列型、`foaf:age`として整数型のオプションプロパティを持つ」という意味です。 SHACL : SHACLはRDFグラフ上で定義 され、検証ロジックをRDFのトリプル形式 で記述します。 SPARQLクエリを使って検証を行うことができるため、より複雑なルールや推論が可能です。 例(Turtle形式): ex:PersonShape a sh:NodeShape ; sh:targetClass foaf:Person ; sh:property [ sh:path foaf:name ; sh:datatype xsd:string ; ] ; sh:property [ sh:path foaf:age ; sh:datatype xsd:integer ; sh:maxCount 1 ; ] . * これは「`PersonShape`は、`foaf:name`として文字列型、`foaf:age`として整数型で1つまでのプロパティを持つ」という意味です。 3. 検証方法 ShEx : ...

2024年10月25日 · 1 分 · Nakamura

Omeka SのModel Viewerモジュールを試す

概要 Model Viewerは、Omeka S向けのモジュールであり、3Dモデル用のビューワーであるthree.jsを統合しています。 https://github.com/Daniel-KM/Omeka-S-module-ModelViewer 本記事では、本モジュールの使用方法について説明します。 参考 以下で、IIIFを用いた3Dモデルの公開方法を紹介しています。こちらも参考にしてください。 インストール 他の一般的なモジュールのインストール方法と同じです。 使用方法 メディアの詳細画面において、以下のように、3Dビューアが表示されます。 まとめ 操作性についてはUniversal Viewerのほうが優れているように感じましたが、参考になりましたら幸いです。

2024年10月18日 · 1 分 · Nakamura

@samvera/rampビューアのFiles/Markersタブの使用方法

概要 IIIF Audio/Visualに対応したビューアの一つである@samvera/rampビューアのFiles/Markersタブの使用方法について調べたので備忘録です。 マニュアル Filesについては、以下に記載がありました。 https://samvera-labs.github.io/ramp/#supplementalfiles また、Markersについては、以下に記載があります。 https://samvera-labs.github.io/ramp/#markersdisplay 使用するデータ 『県政ニュース 第1巻』(県立長野図書館)を使用します。 https://www.ro-da.jp/shinshu-dcommons/library/02FT0102974177 Filesタブ renderingプロパティを読むと記載されています。renderingプロパティについては、以下のCookbookにも掲載されています。 https://iiif.io/api/cookbook/recipe/0046-rendering/ 以下のようなスクリプトにより、マニフェストファイルにrenderingプロパティを追加します。 def add_rendering(self, manifest_path): manifest = self.load_manifest(manifest_path) japan_search_id = manifest.homepage[1].id.split("/")[-1] japan_search_api_url = f"https://jpsearch.go.jp/api/item/{japan_search_id}" rendering = ResourceItem( label={ "ja": ["アイテム参照API"], }, id=japan_search_api_url, type="Dataset", format="application/json" ) manifest.rendering = rendering output_path = f"{self.input_dir}/manifest_rendering.json" with open(output_path, "w") as f: f.write(manifest.json(indent=2)) return output_path 以下のようなマニフェストファイルが作成されます。 { "@context": "http://iiif.io/api/presentation/3/context.json", "id": "https://d1u7hq8ziluwl9.cloudfront.net/sdcommons_npl-02FT0102974177/manifest.json", "type": "Manifest", "label": { "ja": [ "県政ニュース 第1巻" ] }, "requiredStatement": { "label": { "ja": [ "Attribution" ] }, "value": { "ja": [ "『県政ニュース 第1巻』(県立長野図書館)を改変" ] } }, "rendering": [ { "id": "https://jpsearch.go.jp/api/item/sdcommons_npl-02FT0102974177", "type": "Dataset", "label": { "ja": [ "アイテム参照API" ] }, "format": "application/json" } ], ... } ビューアでの表示例は以下です。 ...

2024年10月17日 · 3 分 · Nakamura

Omeka SのOAI-PMHリポジトリのresumptionTokenの不具合への対応

概要 Omeka SのOAI-PMHリポジトリのresumptionTokenにおいて、有効期限内にもかかわらず、以下のように、[badResumptionToken]が出力される事例に遭遇しました。 この不具合への対処方法についてメモします。 対応 以下のファイルについて、$currentTimeと$expirationTimeを比較する処理を加えたところ、有効期限内のtokenが残るようになりました。 ... private function resumeListResponse($token): void { $api = $this->serviceLocator->get('ControllerPluginManager')->get('api'); $expiredTokens = $api->search('oaipmh_repository_tokens', [ 'expired' => true, ])->getContent(); foreach ($expiredTokens as $expiredToken) { $currentTime = new \DateTime(); // 追加 $expirationTime = $expiredToken->expiration(); // 追加 if (!$expiredToken || $currentTime > $expirationTime) { // 追加 $api->delete('oaipmh_repository_tokens', $expiredToken->id()); } // 追加 } 上記のような対応をしなくてもうまくいくケースがあったので、PHPのバージョン等による違いがあるのかもしれません。 参考 参考までに、OAI-PMHリポジトリモジュールによって作成されるテーブルの中身を確認しました。 ...

2024年10月10日 · 2 分 · Nakamura

(非標準)Omeka SのOAI-PMH RepositoryモジュールでDeleteレコードを出力してみる

概要 Omeka SのOAI-PMH RepositoryモジュールでDeleteレコードを出力してみましたので、備忘録です。 背景 以下のモジュールを使用することにより、OAI-PMHのリポジトリ機能を構築することができます。 https://omeka.org/s/modules/OaiPmhRepository/ ただ、確認した限り、Deleteレコードを出力する機能はないようでした。 関連モジュール Omekaの標準機能では、削除されたリソースを保存する機能はないかと思います。 一方、以下のモジュールは削除されたリソースを保持する機能を追加します。 https://github.com/biblibre/omeka-s-module-Necropolis 本モジュールを有効化することにより、以下のように、リソースがいつ誰によって削除されたかを記録できるようになりました。 OAI-PMH Repositoryモジュールへの応用 上記のモジュールで作成される削除されたリソースの情報が格納されるテーブルを使って、Deleteレコードの出力を試みます。 以下のファイルのlistResponse関数に追記します。 private function listResponse($verb, $metadataPrefix, $cursor, $set, $from, $until): void { /** * @var \Omeka\Api\Adapter\Manager $apiAdapterManager * @var \Doctrine\ORM\EntityManager $entityManager */ $apiAdapterManager = $this->serviceLocator->get('Omeka\ApiAdapterManager'); $entityManager = $this->serviceLocator->get('Omeka\EntityManager'); $itemRepository = $entityManager->getRepository(\Omeka\Entity\Item::class); $qb = $itemRepository->createQueryBuilder('omeka_root'); $qb->select('omeka_root'); $query = new ArrayObject; $expr = $qb->expr(); // 以下を追加 if ($set === 'o:deleted') { $settings = $this->serviceLocator->get('Omeka\Settings'); $namespaceId = $settings->get('oaipmhrepository_namespace_id', 'default_namespace'); // 削除済みレコードを necropolis_resource テーブルから取得する $deletedResourceRepository = $entityManager->getRepository(\Necropolis\Entity\NecropolisResource::class); // カスタムエンティティ // oaipmhrepository_expose_mediaに応じて、mediaとitemの取得を分ける $exposeMedia = $settings->get('oaipmhrepository_expose_media', false); // デフォルトはfalse(itemのみ) if ($exposeMedia) { $qb = $deletedResourceRepository->createQueryBuilder('necropolis_resource'); } else { // Itemのみを取得する $qb = $deletedResourceRepository->createQueryBuilder('necropolis_resource') ->andWhere('necropolis_resource.resourceType = :itemType') ->setParameter('itemType', 'Omeka\Entity\Item'); } // 日付フィルタリング if ($from) { $qb->andWhere($expr->gte('necropolis_resource.deleted', ':from')); $qb->setParameter('from', $from); } if ($until) { $qb->andWhere($expr->lte('necropolis_resource.deleted', ':until')); $qb->setParameter('until', $until); } // 結果の制限とオフセット $qb->setMaxResults($this->_listLimit); $qb->setFirstResult($cursor); $paginator = new Paginator($qb, false); $rows = count($paginator); if ($rows == 0) { $this->throwError(self::OAI_ERR_NO_RECORDS_MATCH, new Message('No records match the given criteria.')); // @translate } else { if ($verb == 'ListIdentifiers') { $method = 'appendHeader'; } elseif ($verb == 'ListRecords') { $method = 'appendRecord'; } $verbElement = $this->document->createElement($verb); $this->document->documentElement->appendChild($verbElement); foreach ($paginator as $deletedEntity) { // 削除されたリソースの情報をOAI-PMHレスポンスに追加 $header = $this->document->createElement('header'); $header->setAttribute('status', 'deleted'); // 削除済みレコードとして設定 $identifier = $this->document->createElement('identifier', 'oai:' . $namespaceId . ":" . $deletedEntity->getId()); $header->appendChild($identifier); $datestamp = $this->document->createElement('datestamp', $deletedEntity->getDeleted()->format('Y-m-d\TH:i:s\Z')); $header->appendChild($datestamp); $verbElement->appendChild($header); } // Resumption Token の処理 if ($rows > ($cursor + $this->_listLimit)) { $token = $this->createResumptionToken($verb, $metadataPrefix, $cursor + $this->_listLimit, $set, $from, $until); $tokenElement = $this->document->createElement('resumptionToken', (string) $token->id()); $tokenElement->setAttribute('expirationDate', $token->expiration()->format('Y-m-d\TH:i:s\Z')); $tokenElement->setAttribute('completeListSize', (string) $rows); $tokenElement->setAttribute('cursor', (string) $cursor); $verbElement->appendChild($tokenElement); } elseif ($cursor != 0) { $tokenElement = $this->document->createElement('resumptionToken'); $verbElement->appendChild($tokenElement); } } return; } ... OAI-PMH標準には合致していない実装方法ですが、setにo:deletedを指定すると、削除レコードを返却することができます。 ...

2024年10月10日 · 2 分 · Nakamura

iiif-prezi3を使って、動画に目次を付与する

概要 iiif-prezi3を使って、動画に目次を付与する方法に関する備忘録です。 セグメントの検出 Amazon Rekognitionのビデオセグメントの検出を用います。 https://docs.aws.amazon.com/ja_jp/rekognition/latest/dg/segments.html 以下などでサンプルコードが公開されています。 https://docs.aws.amazon.com/ja_jp/rekognition/latest/dg/segment-example.html 使用するデータ 『県政ニュース 第1巻』(県立長野図書館)を使用します。 https://www.ro-da.jp/shinshu-dcommons/library/02FT0102974177 マニフェストファイルへの反映 以下の記事などを参考に、マニフェストファイルが作成済みであるとします。 以下のようなスクリプトにより、マニフェストファイルにvttファイルを追加します。 from iiif_prezi3 import Manifest, AnnotationPage, Annotation, ResourceItem, config, HomepageItem, KeyValueString #| export class IiifClient: def load_manifest(self, manifest_path): with open(manifest_path, "r") as f: manifest_json = json.load(f) manifest = Manifest(**manifest_json) return manifest def add_segment(self, manifest_path): manifest = self.load_manifest(manifest_path) path = f"{self.input_dir}/output_segment.json" with open(path, "r") as f: data = json.load(f) range_id = f"{self.prefix}/range" range_toc = manifest.make_range( id=f"{range_id}/r", label="Table of Contents" ) canvas_id = manifest.items[0].id for s in data["Segments"]: s_type = s["Type"] if s_type != "SHOT": continue index = s["Shot Index"] start = s["Start Timestamp (milliseconds)"] / 1000 end = s["End Timestamp (milliseconds)"] / 1000 range_seg = range_toc.make_range( id=f"{range_id}/r{index}", label=f"Segment {index}", ) range_seg.items.append({ "id": f"{canvas_id}#t={start},{end}", "type": "Canvas" }) output_path = f"{self.input_dir}/manifest_segment.json" with open(output_path, "w") as f: f.write(manifest.json(indent=2)) return output_path 以下のようなマニフェストファイルが作成されます。 ...

2024年10月9日 · 2 分 · Nakamura

iiif-prezi3を使って、動画に字幕を設定する

概要 iiif-prezi3を使って、動画に字幕を設定する方法に関する備忘録です。 字幕の作成 OpenAIのAPIを使用して字幕ファイルを作成しました。動画のファイルを音声ファイルに変換しています。 from openai import OpenAI from pydub import AudioSegment from dotenv import load_dotenv class VideoClient: def __init__(self): load_dotenv(verbose=True) api_key = os.getenv("OPENAI_API_KEY") self.client = OpenAI(api_key=api_key) def get_transcriptions(self, input_movie_path): audio = AudioSegment.from_file(input_movie_path) # 一時ファイルにオーディオを書き込む with tempfile.NamedTemporaryFile(suffix=".mp3") as temp_audio_file: audio.export(temp_audio_file.name, format="mp3") # MP3形式でエクスポート temp_audio_file.seek(0) # ファイルポインタを先頭に戻す # Whisper APIでトランスクリプトを取得 with open(temp_audio_file.name, "rb") as audio_file: # Whisper APIでトランスクリプトを取得 transcript = self.client.audio.transcriptions.create( model="whisper-1", file=audio_file, response_format="vtt" ) return transcript 使用するデータ 『県政ニュース 第1巻』(県立長野図書館)を使用します。 https://www.ro-da.jp/shinshu-dcommons/library/02FT0102974177 マニフェストファイルへの反映 以下の記事などを参考に、マニフェストファイルが作成済みであるとします。 以下のようなスクリプトにより、マニフェストファイルにvttファイルを追加します。 from iiif_prezi3 import Manifest, AnnotationPage, Annotation, ResourceItem, config, HomepageItem, KeyValueString #| export class IiifClient: def load_manifest(self, manifest_path): with open(manifest_path, "r") as f: manifest_json = json.load(f) manifest = Manifest(**manifest_json) return manifest def add_vtt(self, manifest_simple_path): manifest = self.load_manifest(manifest_simple_path) vtt_url = f"{self.prefix}/video.vtt" canvas = manifest.items[0] vtt_anno_page = AnnotationPage(id=f"{canvas.id}/page2") canvas.annotations = [ vtt_anno_page, ] vtt_body = ResourceItem(id=vtt_url, type="Text", format="text/vtt") vtt_anno = Annotation( id=f"{vtt_anno_page.id}/a1", motivation="supplementing", body=vtt_body, target=canvas.id, label = "WebVTT Transcript (machine-generated)" ) vtt_anno_page.add_item(vtt_anno) with open(f"{self.input_dir}/manifest_vtt.json", "w") as f: f.write(manifest.json(indent=2)) 以下のようなマニフェストファイルが作成されます。 ...

2024年10月9日 · 2 分 · Nakamura

iiif-prezi3を使って、動画にアノテーションを付与する

概要 iiif-prezi3を使って、動画にアノテーションを付与する方法に関する備忘録です。 アノテーションの付与 Amazon Rekognitionのlabel detectionを用います。 https://docs.aws.amazon.com/rekognition/latest/dg/labels.html?pg=ln&sec=ft 以下などでサンプルコードが公開されています。 https://docs.aws.amazon.com/ja_jp/rekognition/latest/dg/labels-detecting-labels-video.html 特に、GetLabelDetectionにおける集計をSEGMENTSにすることで、StartTimestampMillisとEndTimestampMillisを得ることができます。 ただし、以下の点に注意が必要です。 SEGMENTS による集計の場合、境界ボックス付きの検出されたインスタンスに関する情報は返されません。 使用するデータ 『県政ニュース 第1巻』(県立長野図書館)を使用します。 https://www.ro-da.jp/shinshu-dcommons/library/02FT0102974177 マニフェストファイルへの反映 以下の記事などを参考に、マニフェストファイルが作成済みであるとします。 以下のようなスクリプトにより、マニフェストファイルにvttファイルを追加します。 from iiif_prezi3 import Manifest, AnnotationPage, Annotation, ResourceItem, config, HomepageItem, KeyValueString #| export class IiifClient: def load_manifest(self, manifest_path): with open(manifest_path, "r") as f: manifest_json = json.load(f) manifest = Manifest(**manifest_json) return manifest def add_label_segment(self, manifest_path): manifest = self.load_manifest(manifest_path) label_path = f"{self.input_dir}/output_label_seg.json" with open(label_path, "r") as f: label_seg = json.load(f) canvas = manifest.items[0] labels = label_seg["Labels"] anno_page_id = f"{canvas.id}/page1" anno_page = AnnotationPage(id=anno_page_id) canvas.annotations.append(anno_page) for i in range(len(labels)): label = labels[i] start = label["StartTimestamp"] / 1000 end = label["EndTimestamp"] / 1000 name = label["Label"]["Name"] anno_id = f"{anno_page_id}/a{i}" anno = Annotation( id=anno_id, motivation="tagging", target=canvas.id + "#t=" + str(start) + "," + str(end), body={ "type": "TextualBody", "value": name, "format": "text/plain", } ) anno_page.add_item( anno ) output_path = f"{self.input_dir}/manifest_label_seg.json" with open(output_path, "w") as f: f.write(manifest.json(indent=2)) return output_path 以下のようなマニフェストファイルが作成されます。 ...

2024年10月9日 · 2 分 · Nakamura

GakuNin RDMとAmazon S3を接続し、Archivematicaでファイルを処理する

概要 GakuNin RDMとAmazon S3を接続し、Archivematicaでファイルを処理する方法に関する備忘録です。 https://rcos.nii.ac.jp/service/rdm/ 背景 以下の記事で、ArchivematicaでAmazon S3を処理対象とする方法を記載しました。 これにより、指定したバケットにファイルやフォルダをアップロードすることにより、それらをArchivematicaの処理対象として、AIPやDIPを作成することができます。 ただし、このままではプロジェクトのメンバー毎にIAMユーザを作成する必要がありました。 GakuNin RDMの利用 今回はメンバー全員がGakuNin RDMのプロジェクトのメンバーとして登録されていました。 そこで、プロジェクトにAmazon S3を接続して、GakuNin RDMからS3にファイルをアップロードできるようにしてみます。 これにより、IAMユーザの管理が不要になります。 設定方法 アドオンを選択します。 Amazon S3を有効にします。 IAMユーザで作成したアクセスキーIDとシークレットアクセスキーを入力することで、バケットの一覧が表示されます。 結果、GakuNin RDMからAmazon S3にファイルをアップロードできるようになりました。 Archivematicaからも同バケットを以下のように参照できるため、ここからAIPなどを作成することができます。 まとめ GakuNin RDMを利用可能な方に限られてしまいますが、参考になりましたら幸いです。

2024年10月9日 · 1 分 · Nakamura

iiif-prezi3を使って、動画に関するIIIF v3マニフェストを作成する

概要 iiif-prezi3を使って、動画に関するIIIF v3マニフェストを作成する機会がありましたので、備忘録です。 https://github.com/iiif-prezi/iiif-prezi3 参考 IIIFマニフェストファイルの例、およびiiif-prezi3を使った実装例は、IIIF Cookbookで公開されています。 以下、動画に関するIIIF v3マニフェストを作成する例です。 https://iiif.io/api/cookbook/recipe/0003-mvm-video/ iiif-prezi3を使った実装例は以下で公開されています。 https://iiif-prezi.github.io/iiif-prezi3/recipes/0003-mvm-video/ from iiif_prezi3 import Manifest, AnnotationPage, Annotation, ResourceItem, config config.configs['helpers.auto_fields.AutoLang'].auto_lang = "en" manifest = Manifest(id="https://iiif.io/api/cookbook/recipe/0003-mvm-video/manifest.json", label="Video Example 3") canvas = manifest.make_canvas(id="https://iiif.io/api/cookbook/recipe/0003-mvm-video/canvas") anno_body = ResourceItem(id="https://fixtures.iiif.io/video/indiana/lunchroom_manners/high/lunchroom_manners_1024kb.mp4", type="Video", format="video/mp4") anno_page = AnnotationPage(id="https://iiif.io/api/cookbook/recipe/0003-mvm-video/canvas/page") anno = Annotation(id="https://iiif.io/api/cookbook/recipe/0003-mvm-video/canvas/page/annotation", motivation="painting", body=anno_body, target=canvas.id) hwd = {"height": 360, "width": 480, "duration": 572.034} anno_body.set_hwd(**hwd) hwd["width"] = 640 canvas.set_hwd(**hwd) anno_page.add_item(anno) canvas.add_item(anno_page) print(manifest.json(indent=2)) まとめ 他にも多くのサンプルや実装例が公開されています。参考になりましたら幸いです。

2024年10月8日 · 1 分 · Nakamura

Next.jsでアンダースコアから始まるURLセグメントを利用する

概要 </api/_search> のようなAPIを作成するにあたり、アンダースコアから始まるURLセグメントを作成する方法を調べましたので、備忘録です。 方法 以下に記載がありました。 https://nextjs.org/docs/app/building-your-application/routing/colocation#:~:text=js file conventions.-,Good to know,-While not a 以下、日本語訳です。 URLセグメントでアンダースコアから始まる部分を作成するには、フォルダー名に %5F(アンダースコアのURLエンコード形式)を付けてください。例: %5FfolderName。 /api/%5Fsearch/route.ts のようにファイルを作成することで、解決することができました。 まとめ 参考になりましたら幸いです。

2024年10月8日 · 1 分 · Nakamura

@elastic/search-uiのsetFilterの不具合対応

概要 @elastic/search-uiのsetFilterについて、不具合が報告されています。 https://github.com/elastic/search-ui/issues/1057 この不具合について、すでに以下のコミットで修正されています。 https://github.com/elastic/search-ui/pull/1058 ただ、2024年10月7日時点において、上記の対応がなされた最新版がリリースされていません。 そこで、独自にビルドしてリリースすることを試みましたので、その手順に関する備忘録です。 修正 まず、リポジトリをフェッチしました。 https://github.com/nakamura196/search-ui そして、以下の修正を加えました。 https://github.com/nakamura196/search-ui/commit/f7c7dc332086ca77a2c488f3de8780bbeb683324 具体的には、package.jsonと.npmrcに変更を加えました。 パッケージの公開 以下の手順により、パッケージを公開します。 1. GitHub パーソナルアクセストークンを生成 GitHub Package Registry にアクセスするためには、GitHub のパーソナルアクセストークンが必要です。以下の手順でトークンを作成します: GitHubのアカウントにログインします。 右上のプロフィール画像をクリックし、「Settings」を選択。 左側のメニューから「Developer settings」を選択。 「Personal access tokens (classic)」を選択し、「Generate new token」をクリック。 必要な権限(write:packages と read:packages など)を選択して、トークンを生成します。 2. npm にログイン 次に、GitHub Package Registry にログインするために以下のコマンドを実行します: npm login --registry=https://npm.pkg.github.com 実行すると、以下の情報が要求されます: Username: GitHub のユーザー名 Password: 先ほど生成したパーソナルアクセストークン Email: GitHub に登録しているメールアドレス 3. パッケージを再度公開 認証が完了したら、再度 npm publish を実行してパッケージを公開します。 npm publish --registry=https://npm.pkg.github.com 結果、以下のようなページが作成されました。 利用 利用するリポジトリにおいて、以下を作成します。vercelでのビルドを想定して、GITHUB_TOKENは環境変数から参照するようにしました。 @nakamura196:registry=https://npm.pkg.github.com //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN} まとめ 結局上記の方法では、もともとの課題であったsetFilterの修正はうまくいかなかったのですが、パッケージの公開方法に関して、参考になる部分がありましたら幸いです。 ...

2024年10月7日 · 1 分 · Nakamura

Omeka Sのテーマの一覧を視覚的に確認するページを作成するプログラム

概要 以下の記事で、Omeka Sのテーマの一覧を視覚的に確認するページを紹介しました。 上記のページ作成に使用したプログラムを以下のリポジトリで公開しました。 https://github.com/nakamura196/OmekaS まとめ 同様の作業を行うにあたり、参考になりましたら幸いです。

2024年10月5日 · 1 分 · Nakamura

rico-converterを試す

概要 rico-converterを試す機会がありましたので、備忘録です。 https://github.com/ArchivesNationalesFR/rico-converter 以下のように説明されています。 A tool to convert EAC-CPF and EAD 2002 XML files to RDF datasets conforming to Records in Contexts Ontology (RiC-O) (機会翻訳)EAC-CPFやEAD 2002のXMLファイルを、Records in Contexts Ontology(RiC-O)に準拠したRDFデータセットに変換するためのツール 変換する 以下に説明があります。 https://archivesnationalesfr.github.io/rico-converter/en/GettingStarted.html まず、以下から最新のzipファイルをダウンロードして、展開します。 https://github.com/ArchivesNationalesFR/rico-converter/releases/latest サンプルデータとして、input-eacとinput-eadがあるので、これらをRDFに変換します。 input-eac 本フォルダ内にあるinput-eac/FRAN_NP_051151.xmlについて、ChatGPTによる解説は以下です。 ! このXMLファイルは、EAC-CPF(Encoded Archival Context for Corporate Bodies, Persons, and Families)という形式で記述されており、フランスの文化省(Ministère de la Culture)などの組織に関する情報を体系的に整理したものです。主な要素は次の通りです: • Controlセクション: レコードに関するメタデータを含んでいます。これには、レコードID、言語の宣言、更新履歴、使用した資料の出典などが含まれます。 • Identityセクション: 法人(ここではフランスの文化省)に関する基本的な情報が記載されています。文化省の名前が変遷してきたことを示す複数の名称や、各名称が使われていた期間も詳細に記述されています。 • Descriptionセクション: 文化省の存在期間、法的地位、主要な機能、任務、さらにその歴史的な変遷に関する詳細な説明が含まれています。たとえば、1959年に設立され、法令に基づいてどのように運営されてきたか、組織の構造がどのように変化したかが説明されています。 • Relationsセクション: 他の組織や人物との関係を示しています。ここには、フランス国内の関連機関や教育機関、さらには歴代の文化相との関係が詳細に記載されており、外部のリソース(アーカイブやオンラインの情報)へのリンクも含まれています。 このXMLは、アーカイブや組織に関する情報を標準化された形式で記述し、他のデータベースやシステムと連携することを目的としています。特に、組織の歴史や関係性を追跡する際に有用です。 以下を実行します。 sh ricoconverter.sh デフォルトのまま実行すると、output-eac-20241005といったフォルダが作成され、その中にRDFファイルが出力されます。 input-ead 本フォルダ内にあるinput-ead/FRAN_IR_003500.xmlについて、ChatGPTによる解説は以下です。 ...

2024年10月5日 · 3 分 · Nakamura