PXMLT5PEでTVTestを見れるようにする
Windows10でTVtestをTS抜き視聴環境に使っていたが、Windows11へのアップデートで一部のBonDriver以外は見れなくなってしまい、しばらくぶりに使うと「BonDriverの初期化に失敗しました」と表示されTVTestで全く視聴ができなくなってしまった。Geminiと相談し、最初からやり直してTVTestを視聴できる環境を作ったが、いくつか躓いたため過程を示す。
参考
- https://github.com/tsukumijima/DTV-Builds ビルド済みTVTest、EDCBの入手
- https://blog.tsukumijima.net/article/tvtest-usage/#toc14 TVtest導入手順使い方
- https://blog.tsukumijima.net/article/ts-dtv-soft/ ビルド済みTVTestの入手
- Gemini Pro先生
TVTestを入手する
- https://github.com/tsukumijima/DTV-Buildsの「ダウンロード」の「TVTest-0.10.0-251212 … TVTest-0.10.0-251212.zip」等の箇所からダウンロードする。
- zipファイルを展開し、cert-install.jseをダブルクリックして証明書をインストールする。失敗する場合はcert-uninstall.jseを実行した後cert-install.jseを実行する。
BonDriverを入手する
安定性を高めるため、外部カードリーダーを用い非公式のBonDriverを使用する。
- 記事https://blog.tsukumijima.net/article/ts-dtv-soft/の「px4_drv for WinUSB (2025/01/11) … px4_drv_winusb-250111.zip (ダウンロード)」の箇所から非公式BonDriverダウンロードする。
- zipファイルを展開する
- DriverフォルダのPX-MLT5PE.infを右クリック、「インストール」を選択しドライバをインストールする
古いドライバを削除する
- 検索欄に「デバイスマネージャー」と入力してデバイスマネージャーを開く。
- 「サウンド、ビデオ、およびゲーム コントローラー」にMLT5PEがあるので*「デバイスのアンインストール」を選択する。このとき、「このデバイスのドライバーを削除しようとしました」というチェックを入れてアンインストールを実行する。
- デバイスマネージャー上部のメニューから「操作」>「ハードウェア変更のスキャン」をクリックする
TVTestを起動する
- TVtest.exeをダブルクリックして起動する。ただしBonDriverにBonDriver_PX-MLT.dllを選択、MPEG-2デコーダを[TVTest DTV Video Decoder] に変更する、映像レンダラを[EVR] に変更する。
付録 EDCBを導入し、ブラウザで視聴できるようにする
- https://github.com/tsukumijima/DTV-Buildsまたはhttps://blog.tsukumijima.net/article/ts-dtv-soft/#toc21でビルド済みEDCBをダウンロードする。zipファイルのダウンロードは失敗するが、ダウンロードされた一時ファイルの拡張子をエクスプローラーで.zipに書き換えると解凍、展開できる。
- https://blog.tsukumijima.net/article/new-bs-channel-settings/#toc10 の手順に従いチャンネルスキャン等EDCB初期設定を行う。
- EpgTimerの設定>録画動作>録画、EPG取得終了後のデフォルト動作を「何もしない」にする。こうしないと普段使いのPCで録画を行う場合録画終了と同時にPCがスリープし不便。
- EpgTimerの設定>外部アプリケーション>TVTest連系>視聴に使用するBonDriverにBonDriverを追加。NetworkTVモードにチェック。これでブラウザと通信可能になる。
- EpgCapBonの設定>ネットワーク設定>TCP送信先に0.0.0.1:0-29(SrvPipe)を追加
- ブラウザでlocalhost:5510/EMWUI>リモート視聴にアクセスし、設定>画質>720p/h264/ffmpeg(一番上のもの)を選択する。これはCPUエンコードであり多くのPCで実行しやすい。
付録 Konomi TVを導入する
WebUIではKonomi TVより良いものはないと思う
https://github.com/tsukumijima/KonomiTVの指示に従って導入する
wsl上のUbuntuのdiskpartのexpandに失敗しvhdxの読み取り以外でのマウントができなくなったが、qemu-imgで復旧した
状況
windows11のwsl2で、4TBのSSDにdockerでUbuntuを動かし、seafile, immichをホストしている。容量不足で落ちてしまったので、diskpartでexpandを行ったがエラーが出た。diskpartを終了した後も、以下のエラーを出してUbuntuが起動しなくなった。
(base) PS C:\WINDOWS\system32> wsl -d Ubuntu ディスク 'I:\WSL\Ubuntu\ext4.vhdx' を WSL2 にアタッチできませんでした: ファイルまたはディレクトリが壊れているため、読み 取ることができません。 エラー コード: Wsl/Service/CreateInstance/MountDisk/HCS/0x80070570
前提
diskpartを用いてvhdxファイルをディスクをとしてアタッチしようとすると、readonlyではアタッチできるが書き込み可能な状態ではアタッチできない。そのため、おそらくexpandが何らかの理由で途中で終了し、vhdxのメタデータと中身の整合性が取れず、windowsの安全機構によりWindowsのディスクapi経由では書き込みが不可能になっていると推定した。Debianなど他のOSをwslでインストールしても、wslがWindowsのapiでディスクを認識するため、vhdxをアタッチしても修復ツールを用いることができなかった。(鶏と卵状態) Geminiと問答を繰り返しいくつか解決法を探ったが最終的に以下の方法で解決した。
解決策
仮想マシンを用いてwin apiを迂回する。以下管理者権限power shell上の操作
QEMUでの操作
QEMU for Windows (Weilnetz)の最新版をダウンロードする。https://qemu.weilnetz.de/w64/にアクセスし、qemu-w64-setup-20260422.exeのようなファイルをダウンロードする。 インストーラーを実行し、qemuをインストールする。
qemuがインストールされたフォルダに移動し、VHDXファイルの修復を試みる。
cd "C:\Program Files\qemu" .\qemu-img.exe check -r all "I:\WSL\Ubuntu\ext4.vhdx"
- するとこのようなエラーが出た
(base) PS C:\WINDOWS\system32> wsl --shutdown (base) PS C:\WINDOWS\system32> cd "C:\Program Files\qemu" (base) PS C:\Program Files\qemu> .\qemu-img.exe check -r all "I:\WSL\Ubuntu\ext4.vhdx" C:\Program Files\qemu\qemu-img.exe: VHDX BAT entry 101640 start offset 3785442197504 points after end of file (3784165031936). Image has probably been truncated. C:\Program Files\qemu\qemu-img.exe: VHDX BAT entry 101641 start offset 3785443246080 points after end of file (3784165031936). Image has probably been truncated. C:\Program Files\qemu\qemu-img.exe: VHDX BAT entry 101642 start offset 3785444294656 points after end of file (3784165031936). Image has probably been truncated. C:\Program Files\qemu\qemu-img.exe: VHDX BAT entry 101804 start offset 3784883306496 points after end of file (3784165031936). Image has probably been truncated. C:\Program Files\qemu\qemu-img.exe: VHDX BAT entry 101805 start offset 3784884355072 points after end of file (3784165031936). Image has probably been truncated. 5 errors were found on the image. Data may be corrupted, or further writes to the image may corrupt it. (base) PS C:\Program Files\qemu>
ファイルの末尾の長さがインデックスで指定されているより短く、エラーが出ている。3785444294656よりファイルが長ければいいので、fsutilを用いてファイル末尾に0を足す。
fsutil file seteof "I:\WSL\Ubuntu\ext4.vhdx" 3786000000000
再度QEMUでチェックする。
.\qemu-img.exe check -r all "I:\WSL\Ubuntu\ext4.vhdx"
以下のように成功する。
ファイル I:\WSL\Ubuntu\ext4.vhdx の eof が設定されました
書き込み可能な状態でマウントする
wsl --mount "I:\WSL\Ubuntu\ext4.vhdx" --vhd --bare
あらかじめインストールしておいたDebianに入る。
wsl -d Debian
ディスクを確認する。
lsblk
このように出てくる。修復したいのは4TBあるsddなので
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS sda 8:0 0 388.6M 1 disk sdb 8:16 0 186M 1 disk sdc 8:32 0 8G 0 disk [SWAP] sdd 8:48 0 3.9T 0 disk sde 8:64 0 1T 0 disk /run/user
e2fsckとresize2fsでメタデータを修復する。
sudo e2fsck -fy /dev/sdd sudo resize2fs /dev/sdd
ctrl+DかexitコマンドでDebianを抜け、アンマウントとUbuntuの起動を行う。
wsl --unmount "I:\WSL\Ubuntu\ext4.vhdx" wsl --shutdown wsl -d Ubuntu
瞬間日記のメールエクスポート形式のテキストをobsidian向けmarkdownに変換するpythonスクリプト例
仕様
- ファイル名はISO形式のyyyy-mm-dd
- 作成日時をファイル作成日時と更新日時に設定
- すべてにタグ#diaryをつける
- 前後の日へのリンクを張る
import re import os import ctypes from datetime import datetime, timedelta from pathlib import Path def parse_diary_entries(text): """日記テキストを日付ごとに分割してパース""" # 日付パターン date_pattern = r'-\s+(\w+),\s+(\w+)\s+(\d+),\s+(\d+)\s+(\d+):(\d+)' # 月名を数字に変換 months = { 'January': 1, 'February': 2, 'March': 3, 'April': 4, 'May': 5, 'June': 6, 'July': 7, 'August': 8, 'September': 9, 'October': 10, 'November': 11, 'December': 12 } entries = [] # すべての日付位置を見つける matches = list(re.finditer(date_pattern, text)) for i, match in enumerate(matches): day_of_week, month, day, year, hour, minute = match.groups() month_num = months.get(month) if not month_num: continue # 日付オブジェクトを作成 date = datetime(int(year), month_num, int(day), int(hour), int(minute)) # 本文の開始位置(日付行の終わり) content_start = match.end() # 本文の終了位置(次の日付の開始、または文末) if i + 1 < len(matches): content_end = matches[i + 1].start() else: content_end = len(text) # 本文を抽出 content = text[content_start:content_end].strip() entries.append({ 'date': date, 'day_of_week': day_of_week, 'time': f"{hour}:{minute}", 'content': content }) return entries def convert_to_markdown(entry_data): """エントリデータをMarkdown形式に変換""" date_iso = entry_data['date'].strftime('%Y-%m-%d') # 前日と翌日の日付を計算 prev_date = (entry_data['date'] - timedelta(days=1)).strftime('%Y-%m-%d') next_date = (entry_data['date'] + timedelta(days=1)).strftime('%Y-%m-%d') # Markdownファイルの内容を生成 markdown = "" # 前後の日付へのリンク markdown += f"[[{prev_date}]] | [[{next_date}]]\n\n" # 本文の改行を保持(Markdownでは行末に2つのスペースで改行) content_lines = entry_data['content'].split('\n') for line in content_lines: if line.strip(): markdown += line + " \n" else: markdown += "\n" # タグを追加 markdown += "\n#diary\n" return date_iso, markdown def set_file_times_windows(filepath, timestamp): """Windowsでファイルの作成日時、更新日時、アクセス日時を設定""" # Windows API の定義 kernel32 = ctypes.windll.kernel32 # 定数 GENERIC_WRITE = 0x40000000 FILE_SHARE_READ = 0x00000001 FILE_SHARE_WRITE = 0x00000002 OPEN_EXISTING = 3 FILE_ATTRIBUTE_NORMAL = 0x80 # ファイルハンドルを開く handle = kernel32.CreateFileW( str(filepath), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, None, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, None ) if handle == -1: print(f"ファイルを開けませんでした: {filepath}") return try: # datetimeをWindowsのFILETIME形式に変換 # FILETIMEは1601年1月1日からの100ナノ秒単位 unix_epoch = datetime(1970, 1, 1) windows_epoch = datetime(1601, 1, 1) # Unixエポックからの秒数を取得 delta = timestamp - windows_epoch # 100ナノ秒単位に変換 filetime = int(delta.total_seconds() * 10000000) # FILETIME構造体を作成 class FILETIME(ctypes.Structure): _fields_ = [ ("dwLowDateTime", ctypes.c_uint32), ("dwHighDateTime", ctypes.c_uint32) ] ft = FILETIME() ft.dwLowDateTime = filetime & 0xFFFFFFFF ft.dwHighDateTime = filetime >> 32 # ファイル時刻を設定(作成日時、アクセス日時、更新日時) kernel32.SetFileTime( handle, ctypes.byref(ft), # 作成日時 ctypes.byref(ft), # アクセス日時 ctypes.byref(ft) # 更新日時 ) finally: kernel32.CloseHandle(handle) def process_diary(input_text, output_dir='diary'): """日記テキストを処理してファイルに出力""" # 出力ディレクトリを作成 Path(output_dir).mkdir(exist_ok=True) # エントリをパース entries = parse_diary_entries(input_text) # 日付順にソート entries.sort(key=lambda x: x['date']) for entry_data in entries: # Markdownに変換 filename, markdown_content = convert_to_markdown(entry_data) # ファイルに書き出し output_path = Path(output_dir) / f"{filename}.md" with open(output_path, 'w', encoding='utf-8') as f: f.write(markdown_content) # Windowsでファイルのタイムスタンプを設定 set_file_times_windows(output_path, entry_data['date']) print(f"作成: {output_path}") # メイン処理 if __name__ == "__main__": # diary.txtを読み込み try: with open('diary.txt', 'r', encoding='utf-8') as f: diary_text = f.read() print("diary.txtを読み込みました") print("変換を開始します...\n") # 変換実行 process_diary(diary_text) print("\n変換が完了しました!") except FileNotFoundError: print("エラー: diary.txtが見つかりません") print("スクリプトと同じディレクトリにdiary.txtを配置してください") except Exception as e: print(f"エラーが発生しました: {e}")
google keepのエクスポート形式のhtmlをobsidian形式のmarkdownに変換するpythonスクリプト例
使い方
python html2md.py example.htmlのようにコマンドを打つとexample.meとなって変換される ディレクトリ内のすべてのhtmlファイルを変換したい場合はfor %i in (*.html) do python html_to_obsidian_md.py "%i"(コマンドプロンプト)Get-ChildItem *.html | ForEach-Object { python html_to_obsidian_md.py $_.Name }(Powershell)とコマンドを打つと良い- 変換先のmdファイルが既にある場合、
-fオプションをつけてpython html2md.py example.html -fとすることで上書きできる
仕様
- タイトルは削除 (e.g.
<div class="title">title</div> ->) - imgタグはmarkdown画像埋め込みに変換 (e.g.
<li><img alt="" src="image.png" /></li> -> ) - タグはハッシュタグに変換 (e.g.
<span class="label-name">namako</span> -> #namako) - 作成日時をファイルの作成日時と更新日時に設定
- ピン止め、アーカイブされたノートはそれぞれpinned, archivedのタグを付加 (e.g.
<span class="pinned" title="Note pinned"> -> #pinned, <span class="archived" title="Note archived"> -> #archived)
スクリプト例
#!/usr/bin/env python3 """ Google Keep HTML エクスポートを Obsidian Markdown に変換するスクリプト 使い方: python html_to_obsidian_md.py input.html [output.md] output.mdを指定しない場合は、入力ファイル名から自動生成されます """ #!/usr/bin/env python3 """ Google Keep HTML エクスポートを Obsidian Markdown に変換するスクリプト 使い方: python html_to_obsidian_md.py input.html [output.md] output.mdを指定しない場合は、入力ファイル名から自動生成されます """ import sys import re from pathlib import Path from html.parser import HTMLParser from html import unescape import argparse class KeepNoteParser(HTMLParser): """Google Keep ノートのHTMLをパースするクラス""" def __init__(self): super().__init__() self.in_heading = False self.in_title = False self.in_content = False self.in_chips = False self.in_chip = False self.in_attachments = False self.in_meta_icons = False self.heading = "" self.title = "" self.content = "" self.labels = [] self.weblinks = [] self.attachments = [] self.is_archived = False self.is_pinned = False self.datetime = "" self.current_chip_text = "" self.current_chip_is_weblink = False self.current_chip_is_label = False self.current_chip_url = "" def handle_starttag(self, tag, attrs): attrs_dict = dict(attrs) if tag == 'div': if attrs_dict.get('class') == 'heading': self.in_heading = True elif attrs_dict.get('class') == 'title': self.in_title = True elif attrs_dict.get('class') == 'content': self.in_content = True elif attrs_dict.get('class') == 'chips': self.in_chips = True elif attrs_dict.get('class') == 'attachments': self.in_attachments = True elif attrs_dict.get('class') == 'meta-icons': self.in_meta_icons = True elif tag == 'span': class_attr = attrs_dict.get('class', '') title_attr = attrs_dict.get('title', '') # アーカイブとピン留めの検出 if 'archived' in class_attr and 'Note archived' in title_attr: self.is_archived = True elif 'pinned' in class_attr and 'Note pinned' in title_attr: self.is_pinned = True # チップの処理 if self.in_chips and 'chip' in class_attr: self.in_chip = True self.current_chip_text = "" self.current_chip_url = "" if 'WEBLINK' in class_attr: self.current_chip_is_weblink = True self.current_chip_is_label = False elif 'label' in class_attr: self.current_chip_is_label = True self.current_chip_is_weblink = False elif tag == 'a' and self.in_chip: self.current_chip_url = attrs_dict.get('href', '') elif tag == 'br' and self.in_content: self.content += '\n' elif tag == 'img' and self.in_attachments: src = attrs_dict.get('src', '') if src: self.attachments.append(src) def handle_endtag(self, tag): if tag == 'div': if self.in_meta_icons: self.in_meta_icons = False elif self.in_title: self.in_title = False elif self.in_content: self.in_content = False elif self.in_chips: self.in_chips = False elif self.in_attachments: self.in_attachments = False elif self.in_heading: self.in_heading = False elif tag == 'span' and self.in_chip: if self.current_chip_is_weblink and self.current_chip_url: self.weblinks.append(self.current_chip_url) elif self.current_chip_is_label and self.current_chip_text: self.labels.append(self.current_chip_text.strip()) self.in_chip = False self.current_chip_is_weblink = False self.current_chip_is_label = False def handle_data(self, data): if self.in_heading and not self.in_title and not self.in_meta_icons: # 日付時刻を抽出( はnarrow no-break space = U+202F) # 例: Oct 28, 2025, 11:16:42 PM date_pattern = '(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)' + r'\s+\d+,\s+\d{4},\s+\d+:\d+:\d+[\s\u202F]+(?:AM|PM)' match = re.search(date_pattern, data) if match: self.datetime = match.group() self.heading += data elif self.in_title: self.title += data elif self.in_content: self.content += data elif self.in_chip: self.current_chip_text += data def set_file_times(filepath: Path, timestamp: float) -> bool: """ ファイルの作成日時と更新日時を設定 Windows、Linux/Mac の両方に対応 Args: filepath: 対象ファイルのパス timestamp: Unix timestamp Returns: 成功したらTrue、失敗したらFalse """ import platform import os try: # Unix系(Linux, Mac)の場合 if platform.system() != 'Windows': # 更新日時とアクセス日時を設定 os.utime(filepath, (timestamp, timestamp)) return True # Windows の場合 import ctypes from ctypes import wintypes # Windows API 定数 GENERIC_WRITE = 0x40000000 OPEN_EXISTING = 3 FILE_SHARE_READ = 0x00000001 FILE_SHARE_WRITE = 0x00000002 # datetime から FILETIME に変換 # FILETIME は 1601年1月1日からの100ナノ秒単位 EPOCH_AS_FILETIME = 116444736000000000 # 1970年1月1日 timestamp_100ns = int((timestamp * 10000000) + EPOCH_AS_FILETIME) # FILETIME 構造体を作成 class FILETIME(ctypes.Structure): _fields_ = [ ("dwLowDateTime", wintypes.DWORD), ("dwHighDateTime", wintypes.DWORD) ] ft = FILETIME() ft.dwLowDateTime = timestamp_100ns & 0xFFFFFFFF ft.dwHighDateTime = timestamp_100ns >> 32 # Windows API 関数の定義 CreateFileW = ctypes.windll.kernel32.CreateFileW CreateFileW.argtypes = [ wintypes.LPCWSTR, # lpFileName wintypes.DWORD, # dwDesiredAccess wintypes.DWORD, # dwShareMode wintypes.LPVOID, # lpSecurityAttributes wintypes.DWORD, # dwCreationDisposition wintypes.DWORD, # dwFlagsAndAttributes wintypes.HANDLE # hTemplateFile ] CreateFileW.restype = wintypes.HANDLE SetFileTime = ctypes.windll.kernel32.SetFileTime SetFileTime.argtypes = [ wintypes.HANDLE, # hFile ctypes.POINTER(FILETIME), # lpCreationTime ctypes.POINTER(FILETIME), # lpLastAccessTime ctypes.POINTER(FILETIME) # lpLastWriteTime ] SetFileTime.restype = wintypes.BOOL CloseHandle = ctypes.windll.kernel32.CloseHandle CloseHandle.argtypes = [wintypes.HANDLE] CloseHandle.restype = wintypes.BOOL # ファイルを開く handle = CreateFileW( str(filepath), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, None, OPEN_EXISTING, 0, None ) if handle == -1: # INVALID_HANDLE_VALUE return False try: # 作成日時、アクセス日時、更新日時をすべて設定 result = SetFileTime(handle, ctypes.byref(ft), ctypes.byref(ft), ctypes.byref(ft)) return bool(result) finally: CloseHandle(handle) except Exception as e: print(f" 警告: ファイル日時の設定に失敗しました: {e}", file=sys.stderr) return False def html_to_markdown(html_path: Path) -> str: """HTMLファイルをMarkdown形式に変換""" # HTMLファイルを読み込み with open(html_path, 'r', encoding='utf-8') as f: html_content = f.read() # HTMLをパース parser = KeepNoteParser() parser.feed(html_content) # Markdownを構築 markdown_lines = [] # タグ(ハッシュタグ形式) tags = [] # アーカイブ/ピン留めタグ if parser.is_archived: tags.append("#archived") if parser.is_pinned: tags.append("#pinned") # ラベルタグ for label in parser.labels: # スペースを含む場合はアンダースコアに置換 tag_name = label.replace(' ', '_') tags.append(f"#{tag_name}") if tags: markdown_lines.append(" ".join(tags) + "\n\n") # 本文 if parser.content: # 連続する改行を整理 content = parser.content.strip() # 3つ以上連続する改行を2つに content = re.sub(r'\n{3,}', '\n\n', content) markdown_lines.append(content + "\n") # Webリンク if parser.weblinks: markdown_lines.append("\n## リンク\n\n") for link in parser.weblinks: markdown_lines.append(f"- {link}\n") # 添付画像(Markdown形式で表示) if parser.attachments: markdown_lines.append("\n## 添付ファイル\n\n") # 重複を除去 unique_attachments = list(dict.fromkeys(parser.attachments)) for attachment in unique_attachments: # Markdown画像記法で表示 markdown_lines.append(f"\n\n") return "".join(markdown_lines), parser.datetime def main(): """メイン処理""" # コマンドライン引数のパース parser = argparse.ArgumentParser( description='Google Keep HTMLエクスポートをObsidian Markdownに変換' ) parser.add_argument('input', help='入力HTMLファイルのパス') parser.add_argument('output', nargs='?', help='出力Markdownファイルのパス(省略可)') parser.add_argument('-f', '--force', action='store_true', help='出力ファイルが既に存在する場合でも上書き') args = parser.parse_args() # 入力ファイルのパス input_path = Path(args.input) if not input_path.exists(): print(f"エラー: ファイルが見つかりません: {input_path}", file=sys.stderr) sys.exit(1) # 出力ファイルのパス if args.output: output_path = Path(args.output) else: # 入力ファイル名から出力ファイル名を生成 output_path = input_path.with_suffix('.md') # 既存ファイルの確認 if output_path.exists() and not args.force: print(f"エラー: 出力ファイルが既に存在します: {output_path}", file=sys.stderr) print("上書きする場合は -f オプションを使用してください", file=sys.stderr) sys.exit(1) # 変換実行 try: markdown_content, datetime_str = html_to_markdown(input_path) # Markdownファイルを書き込み with open(output_path, 'w', encoding='utf-8') as f: f.write(markdown_content) # ファイルの作成日時と更新日時を設定 if datetime_str: try: # 日付文字列をパース (例: Oct 28, 2025, 11:16:42 PM) from datetime import datetime import locale # 英語ロケールを試みる(月名が英語のため) try: locale.setlocale(locale.LC_TIME, 'en_US.UTF-8') except: try: locale.setlocale(locale.LC_TIME, 'C') except: pass # datetimeオブジェクトにパース dt = datetime.strptime(datetime_str, '%b %d, %Y, %I:%M:%S %p') # Unix timestampに変換 timestamp = dt.timestamp() # ファイルの作成日時と更新日時を設定 success = set_file_times(output_path, timestamp) print(f"変換完了: {output_path}") print(f" 作成日時: {datetime_str}") print(f" 出力サイズ: {len(markdown_content)} 文字") if success: print(f" ファイル日時: 設定成功") else: print(f" ファイル日時: 設定失敗(警告)") except Exception as e: print(f"変換完了: {output_path}") print(f" 出力サイズ: {len(markdown_content)} 文字") print(f" 警告: ファイル日時の設定に失敗しました: {e}", file=sys.stderr) else: print(f"変換完了: {output_path}") print(f" 出力サイズ: {len(markdown_content)} 文字") except Exception as e: print(f"エラー: 変換中に問題が発生しました: {e}", file=sys.stderr) sys.exit(1) if __name__ == '__main__': main()
余談
obsidianのコミュニティープラグイン、custom attachment locationが良さそう。画像貼り付けると自動でノート名と同じディレクトリに格納してノートを移動すると画像フォルダも追随する。vault全体の画像のパスも整理してくれる 参考:https://note.com/rock72/n/ne69fd99b193d
Macで、OSの言語設定とは別にOfficeソフト(Word, Excel, Powerpoint)の言語設定を変更する
背景
Macの言語設定を英語にしているが、フォントの選択画面のプレビューなど日本語で表示した方が嬉しい場面が多い。Windows版のOfficeソフトはFile>Optionからソフトの表示言語を変更できるが、Mac版のOfficeソフトはOSの設定言語を表示言語に反映し、ソフトには表示言語を変更するUIがない。そこで調べるとMacでもコマンドラインから表示言語を変更できることを知ったため日本語記事として共有する。
手法
- Terminalで以下のコマンドを実行する
$ defaults write com.microsoft.Powerpoint AppleLanguages '(ja)' $ defaults write com.microsoft.Word AppleLanguages '(ja)' $ defaults write com.microsoft.Excel AppleLanguages '(ja)'
- Activity Monitorでプロセスを終了して再起動すると言語が反映されている(ウィンドウを閉じて再起動するだけでは反映されない)
参考: - コマンド例https://www.reddit.com/r/Colombia/comments/x1sw0j/cambiar_idioma_de_excel_o_powerpoint_o_word_en_un/?tl=ja - コマンド例https://www.lets-talk-about.tech/2018/09/change-software-language-in-microsoft.html - 言語リストhttps://www.ibabbleon.com/iOS-Language-Codes-ISO-639.html
3Dプリンター入門 東京を造る
この記事は[outré Advent Calendar 2025](https://adventar.org/calendars/11982)9日目の記事です。
遅れて大変申し訳ございません。
概要:
3Dプリンター(Bambu Lab P2S)を手に入れたので東京の立体地図を作る。
3Dプリンター入門
へだるさんやendenさんから3Dプリンターの物欲を刺激されたので、創作としては絵や漫画を描いていて必ずしも物理世界での工作力があるわけではない私が、3Dプリンターを買うことに相成った。まず3Dプリンター界について大雑把に述べる。3Dプリンターとは、主に植物性樹脂であるポリ乳酸を熱した金属製ノズルから連続的に押し出して、ノズルが動くことにより、下から積層して物を作っていくという機械で、ポリ乳酸以外にもPET樹脂、ABS樹脂が用いられることもある。この業界は、特許が切れた関係で2010年代から様々な企業が参入したらしい。キーボード界のCherryMXに似ている。Bambu A1 miniという機種が今のところ信頼できる情報源によりコストパフォーマンスが最強とされている。値段とできることは、大抵y=√xみたいな関数になるが、a1 miniはその最初の急な立ち上がりに相当する。上位機種のA1は造形範囲が広くなる。その上のP1Sはエンクロージャー(覆い)がついてABS樹脂など急冷すると反る樹脂を刷ることができる。P2SはP1Sの改良機種で、P1Sと違ってPLA印刷中にドア開けっぱなしにしなくていい、ノズル交換が容易といった違いがある。
Bambu labというのは中国深圳の会社で、ドローンのDJIから2020年に技術者が独立して作った会社らしい。急成長を遂げており、DJIは人材獲得競争でBambu labを仮想敵として競合の3DP企業に出資したと年末騒動になっていた[1]。DJIからスピンアウト起業した企業には他にANKERがあり、中国珠江デルタ地域の製造業の強さに打ちのめされる。情勢が落ち着いたらまた華強北電子城に行きたい。
[1]
日本語訳:https://x.com/yutr0n/status/1989939453226623286?s=46&t=rWvY73qZa5Ie23yU0UA6WA
weixin(削除済み):https://mp.weixin.qq.com/s/qA2zCjLEJ6jCkoxsEP7COg
今回はこのMap2Model[3]というウェブサイトを用いて皇居周辺の地図を印刷する。地図から3DP用の立体モデルを出力するサイトでショート動画[2]のリコメンドで知った。おそらくOpenStreetMapからデータをとってきており標高差は反映されず香港や神戸などを出力すると不自然になる。サイトにアクセスし領域を選択してSTLファイルをダウンロードする。
[2]ショート動画https://www.tiktok.com/@edutechexpert.ua/video/7556536137267809592?_r=1&_t=ZS-912cFtT791k
[3]Map2Model https://map2model.com/
模型であり微細な表現が求められるので、P2Sのノズルを標準の焼き入れ0.4 mm径から別売のステンレス0.2 mm径に交換する。
フィラメントはELEGOO白、ELEGOO灰色、OVERTUREオリーブグリーンを用いる。いずれも2000円前後ですがセールで1500円程度で購入。水路があるので青色が欲しかったがきりがないので進める。いろいろ吟味した結果、灰色を道路に割り当てるより水路に割り当てた方が見栄えがしそうだったので水路に。オリーブグリーンは緑地に使う。
印刷を開始。お濠が美しい。

しばらくするとお濠が白のフィラメントで覆われた、フィラメント切り替えミス?と思ったが首都高がお濠の上を通っているためだと気づいた。都市の解剖学的構造を印刷を眺めながら楽しむ、贅沢だ。P2Sは上がガラス板なのでAMS2を動かすと上からじっくり眺めたり写真が撮れる。
緑地が出てきた、なんて細かいんだ。

建物が生えてきた。最近知った話として、東京駅の正面は皇居に対して微妙に正面を向いていないらしい。

完成。本体にもともと入っているモデルのカメさんを怪獣役に出演させた。こちらのモデルはプラスチックのばね構造で足が反可動になっておりすごい。

「カメ、東京にあらわる」
速習Git + GitHub
特に認証周りでネットの情報が妙に古かったり情報が分散しているため躓いた。ここにショートカットをメモする。 非エンジニア向けに基本的かつ簡潔に書く。
前提
- OS: windows11
- gitコマンドインストール済み
- シェルはコマンドプロンプトを使用
Git入門
ユーザー登録
名前とメールを登録する
- git config --global user.name "Namako Uni"
- git config --global user.email "unamako@gmail.com"
ローカルリポジトリの作成
Gitとは、ファイルの編集履歴を保存するツール。プログラミングのソースコードの管理で主に用いられる。
- gitコマンドをインストールする
- コマンドプロンプトを起動する
- 作業するフォルダを作る(
mkdir neko等) - 作業するフォルダに移動する(
cd neko等) - 作業フォルダにgitを入れる(
git init) - 作業フォルダに何かファイルを作る(
echo nekodesu > readme.md等) - フォルダ内にある全てのファイルをaddする(
git add -A) - フォルダ内にあるaddされたファイルを何かコメントをつけてcommitする(
git commit -m "first commit"等)
メモ:基礎的なコマンド
windowsのコマンドプロンプトの機能としてあるコマンド(シェルコマンド)
- ディレクトリ(フォルダ)を作る:
mkdir フォルダ名 - ディレクトリに移動する
cd フォルダ名 - ディレクトリから出る(一つ上のディレクトリに戻る)
cd .. - ディレクトリにあるものをリストとして表示
dirgitコマンド
- 今どのブランチにいるか何がaddされているか見る:
git status
GitHub入門
参考: - https://www.tohoho-web.com/ex/github.html - https://zenn.dev/nasubita/articles/b4d9cfaf0b6a78 GitHubはローカル環境でGitで管理したファイルをクラウドで共有するサービスである。GitLabとしてセルフホストも可能。GitHubを用いることでインターネットを介して複数人で開発作業ができたり、インターネットに製作したコードを公開できる。
準備
- github.comに行く
- アカウントを作る
- リポジトリを作る
トークンの発行
- github.comにログインする
- メニューバー(アイコン画像をクリックすると現れる)から、Setting>Developer settings>Personal access tokens>Tokens (classic)>Generate new tokenと進みトークンを作成画面を表示する
- repo, admin:repo_hock, delete_repo にチェックを入れる
- Generate tokenをクリックしトークンを発行する
- ghp_sakgjhgJkhgadawliHLiuaslhiuGhjksegみたいな文字列のトークンをコピーしてメモする
ローカルリポジトリをリモートリポジトリに対応させる
- ローカルリポジトリをリモートリポジトリに対応させる
git remote add origin https://namako:ghp_sakgjhgJkhgadawliHLiuaslhiuGhjkseg@github.com/namako/warabimochi.git等 - リモートリポジトリにpushする
git push --set-upstream origin master以降は単にgit pushでよい
その他Gitのコマンド
- git add -Aとgit commitを一度に行う:
git commit -a(ただし新規のファイルについてはaddされない) - ブランチ名aからbに変更:
git branch a b最近はmasterではなくmainをメインのブランチ名として使うことが多いのでgit branch master mainと打つと良い - リモートリポジトリの確認:
git remote -v