2025年1月31日金曜日
AndroidでGoogleフォトの画像をまとめてダウンロード
2023年2月19日日曜日
Googleフォトに保存されているデータ数を把握する方法
概要
Googleフォトにバックアップされている画像枚数を調べる方法を紹介する。
なお、「Googleフォトに保存されているデータ容量を調べる」で紹介している方法ではデータをすべてダウンロードする際などに必要になる容量を確認できる。
容量ではなく、画像の枚数に関してはいくつかの方法で確認することができるが、それぞれ表示される値が異なります。
以降ではそれぞれの確認方法を説明します。
1.Googleダッシュボードでの確認
確認手順
- Safari、Chrome、EdgeなどのWEBブラウザで https://myaccount.google.com/dashboard/ にアクセスする
- 「フォト」欄の「xxx,xxx以上 枚の写真」を確認する。
結果
300,000以上 枚の写真
考察
枚数が少ないうちは正確な数値を確認できるかもしれませんがここでの枚数確認は使い物になりません。
2.AndroidスマホのGmailアプリなどで確認
確認手順
- AndroidのGmailアプリを起動
- 新規メール作成
- 画面上のクリップアイコンを選択
- 「ファイルを添付」を選択
- 「他のアプリでファイルを探す」の中に表示されている「フォト」を選択
- 「写真を選択」画面の「写真」項目の「xxx,xxx個の項目」を確認する。
結果
324,012個の項目
考察
ダッシュボードよりは具体的な数値が表示される。 なお、Googleから教えたもらった内容によると、「Gmail では添付できないタイプのデータである場合は、 Gmail アプリでは検出できずに数値に含まれない可能性がございます。」とのことですが、、、Googleドライブからも同じ手順で確認できて、同じ数値が表示されます。なので添付できない画像があるというリクツは成り立たない感じです。
3.Googleアカウント画面での確認
確認手順
※ アカウントを削除する前の確認画面なので最新の注意を払って操作してください。確認したらアカウントの削除はキャンセルしてください。
- Safari、Chrome、EdgeなどのWEBブラウザで https://myaccount.google.com/deleteaccount にアクセスする
- 「このコンテンツがすべて削除されます」の中を確認
- 「フォト」の項目「xxx,xxx 枚の写真が削除されます」や「他 xxx,xxx件」を確認する。
結果
334,044枚の写真
他 334,041件
考察
この方法で確認した数値が一番大きいが、「他 XXX,XXX件」と表示されている枚数は何なのか謎が多い。もしかしたらここで表示されているのはGoogleフォトだけではなく、Bloggerなどで使用している画像データの枚数も合算されている可能性がある。
4.GoogleTakeoutでダウンロードして確認(調査中)
確認手順[T.B.D.]
- GoogleTakeout画面に遷移
- Googleフォト以外のみにチェックを入れる
- 自動的に作成された年毎のアルバムにチェックを入れる
- アーカイブにもチェックを入れる
- 必要であればゴミ箱にもチェックを入れる
- Exportする
結果
T.B.D.
考察
T.B.D.
結論
GoogleTakeoutでの確認が完了していないが、枚数が少ないときはGoogleダッシュボードで確認できる、ある程度以上枚数が多くなったときは、AndroidのGmailアプリで確認するか、アカウント削除確認画面で確認するしかない感じです。
2022年12月26日月曜日
Google Oneを安く契約する方法
AppleデバイスでGoogleOneを 100GB月200円で契約する方法
GoogleOneは契約方法によって値段が違います、まったく同じサービスが提供されるのであれば少しでも安い方が良いです。どの方法で契約しても手間は変わらないのでぜひ安い方法で契約してください。(Appleデバイスと書いてますが、iPhone以外では確認していません。ご了承ください。)2022年11月29日火曜日
Google Formsでドロップダウン形式の日付を毎日変更する
背景
ソースコート
function updateForm() { // 毎日実行するトリガを設定 setTrigger( arguments.callee.name ); var forms = FormApp.getActiveForm(); var items = forms.getItems(); var table = [ {'title':'発症日','start':-14 ,'end':0 }, // 二週間前から今日まで {'title':'診察希望日1','start':7 ,'end':1 }, // 7日後から明日まで(逆順) {'title':'診察希望日2','start':1 ,'end':7 } // 明日から7日後まで ]; // 途中に日付が変わると面倒なのでここで取得 var today = new Date(); items.forEach( (item) =>{ table.forEach( (range) =>{ // リスト形式で質問がテーブルのtitleと一致したときに処理 if( item.getType() == FormApp.ItemType.LIST && item.getTitle() == range.title ) { item = item.asListItem(); var choices = []; // テーブルの内容に従い選択肢を追加 var dir = (range.start<range.end)?1:-1; for( var i = range.start; (range.start<=i&&i<=range.end)||(range.end<=i&&i<=range.start); i += dir ) { var day = new Date( today ); day.setDate(today.getDate() + i); choices.push( item.createChoice(formatDate( day, "yyyy/MM/dd" ))); } item.setChoices(choices); } }); }); } function formatDate (date, format) { format = format.replace(/yyyy/g, date.getFullYear()); format = format.replace(/MM/g, ('0' + (date.getMonth() + 1)).slice(-2)); format = format.replace(/dd/g, ('0' + date.getDate()).slice(-2)); format = format.replace(/HH/g, ('0' + date.getHours()).slice(-2)); format = format.replace(/mm/g, ('0' + date.getMinutes()).slice(-2)); format = format.replace(/ss/g, ('0' + date.getSeconds()).slice(-2)); format = format.replace(/SSS/g, ('00' + date.getMilliseconds()).slice(-3)); return format; } function setTrigger(name) { // 念のため既存のトリガはすべて削除してから次のトリガーを設定する var triggers = ScriptApp.getProjectTriggers(); triggers.forEach( (trigger) =>{ if( trigger.getHandlerFunction() == name ) { ScriptApp.deleteTrigger(trigger); } }); var setTime = new Date(); setTime.setDate(setTime.getDate() + 1) setTime.setHours(12); setTime.setMinutes(00); ScriptApp.newTrigger(name).timeBased().at(setTime).create(); }
フォームに追加したプルダウン
2022年11月20日日曜日
GoogleTakeoutでダウンロードしたGoogleフォト画像のファイルタイムスタンプを修正するプログラム
以前の記事( https://blog.hikozaru.com/2022/11/googletakeoutgoogle.html)で紹介した作業をもう少し簡単に実行できるようにプログラムを作成したのでそのソースコードを公開する。いろいろ手を抜くために邪道なこと(変数名に日本語を使う)を行っているが、気になる人は好きに修正するとよい。プロジェクト全体が欲しい方はここから、実行できるものが欲しい方はここからダウンロードしてください。
プログラムの大まかな処理の流れは以下です。
- 指定されたファイルもしくは指定されたフォルダ配下のファイルすべてを処理
ただし、JSONファイルは処理をスキップ - ファイル名の後ろに".json"を付加したファイルが存在していたらデシリアライズして撮影日時を取得
- 撮影日時はunixtimeなのでutcを経由してローカル時間に変換
- ファイルの作成日時と更新日時を変更、その処理が成功したらJSONファイルも同様に作成日時と更新日時を変更
- 結果をDataGridに出力
まずはCSファイル
using System; using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Runtime.Serialization; using System.Threading; using System.Windows;
namespace PhotosTimestamp { [DataContract] public class ImageInfo { [DataMember(Name = "photoTakenTime")] public PhotoTakenTime PhotoTakenTime { get; set; } } [DataContract] public class PhotoTakenTime { [DataMember(Name = "timestamp")] public string timstamp { get; set; } } public partial class MainWindow : Window { enum E_RESULT { 更新, 失敗, SKIP }; class Result { public int 番号 { get; set; } public E_RESULT 結果 { get; set; } public string ファイル名 { get; set; } public string エラーメッセージ { get; set; } public string 例外メッセージ { get; set; } } const string HowToUse = "Google TakeoutでダウンロードしたZIPファイルを展開し、展開されたファイル/フォルダをドラッグ&ドロップして下さい。\n" + "コマンドライン引数やプログラムアイコンへのドラッグ&ドロップでもファイル/フォルダ指定可能です。\n" + "画像ファイルにJSONファイル(例:IMG_1234.PNGに対しIMG_1234.PNG.json)がある場合に処理を行います。\n" + "TakeoutでZIPファイルが分割された場合には一つのフォルダに統合してください、画像ファイルとJSONファイルが同一のZIPに格納されるとは限りません。\n" + "JSONファイルに記録されているphotoTakenTime.timestampをLocal時間に変換し、画像ファイルとJSONファイルの作成日時と更新日時に設定します。"; public MainWindow() { InitializeComponent(); var resultList = new ObservableCollection次はXAMLファイル(); resultDataGrid.ItemsSource = resultList; } // 引数で指定されたときは即実行 private void Window_ContentRendered(object sender, EventArgs e) { string[] args = Environment.GetCommandLineArgs(); if (args.Length > 1) { Thread thread = new Thread(new ParameterizedThreadStart(WorkerThread)); thread.Start(args.Skip(1).ToArray()); } else { ddPanel.AllowDrop = true; status.Content = HowToUse; } } // Drag&Drop関連処理 private void ddPanel_PreviewDragOver(object sender, DragEventArgs e) { e.Effects = DragDropEffects.None; if (e.Data.GetDataPresent(System.Windows.DataFormats.FileDrop, true)) { e.Effects = DragDropEffects.Copy; e.Handled = e.Data.GetDataPresent(DataFormats.FileDrop); } } private void ddPanel_Drop(object sender, DragEventArgs e) { if (e.Data.GetDataPresent(DataFormats.FileDrop)) { Thread thread = new Thread(new ParameterizedThreadStart(WorkerThread)); thread.Start(e.Data.GetData(DataFormats.FileDrop)); } } // UIが固まらないようにThreadで処理 void WorkerThread(object arg) { Dispatcher.Invoke((Action)(() => { ddPanel.AllowDrop = false; status.Content = "処理開始"; var dataList = resultDataGrid.ItemsSource as ObservableCollection ; dataList.Clear(); })); DoEntries((string[])arg); Dispatcher.Invoke((Action)(() => { ddPanel.AllowDrop = true; status.Content = "処理が終了しました。\n" + HowToUse; })); } void DoEntries(string[] args) { foreach (string arg in args) { if (File.Exists(arg) == true) { DoFile(arg); } else { try { foreach (var path in Directory.GetFiles(arg, "*", System.IO.SearchOption.AllDirectories)) { DoFile(path); } } catch (Exception e) { Dispatcher.Invoke((Action)(() => { var dataList = resultDataGrid.ItemsSource as ObservableCollection ; dataList.Add(new Result() { 番号=dataList.Count+1, 結果=E_RESULT.失敗, ファイル名 = arg, エラーメッセージ="ファイルまたはフォルダが見つかりませんでした。",例外メッセージ = e.ToString() }); })); } } } } void DoFile(string targetFilename) { var jsonFilename = targetFilename + ".json"; if (string.Compare(".json", System.IO.Path.GetExtension(targetFilename), true) == 0) { Dispatcher.Invoke((Action)(() => { status.Content = targetFilename; })); } else if (System.IO.File.Exists(jsonFilename)) { DateTime photoTakenTime; try { using (var stream = new FileStream(jsonFilename, FileMode.Open, FileAccess.Read)) { var serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(ImageInfo)); var imageInfo = (ImageInfo)serializer.ReadObject(stream); var unixtime = imageInfo.PhotoTakenTime.timstamp; photoTakenTime = DateTimeOffset.FromUnixTimeSeconds(long.Parse(unixtime)).LocalDateTime; } var imageFile = new System.IO.FileInfo(targetFilename); var jsonFile = new System.IO.FileInfo(jsonFilename); var results = new Result[] { new Result(){ 結果=E_RESULT.SKIP,ファイル名=targetFilename,エラーメッセージ="",例外メッセージ="" }, new Result(){ 結果=E_RESULT.SKIP,ファイル名=jsonFilename,エラーメッセージ="画像ファイルの更新に失敗したときはJSONファイルも更新しません。",例外メッセージ="" } }; foreach (var result in results) { try { var file = new System.IO.FileInfo(result.ファイル名); file.CreationTime = photoTakenTime; file.LastWriteTime = photoTakenTime; result.結果 = E_RESULT.更新; result.エラーメッセージ = ""; result.例外メッセージ = ""; } catch (Exception e) { result.結果 = E_RESULT.失敗; result.エラーメッセージ = "タイムスタンプの更新に失敗しました。"; result.例外メッセージ = e.ToString(); break; } } Dispatcher.Invoke((Action)(() => { status.Content = targetFilename; var dataList = resultDataGrid.ItemsSource as ObservableCollection ; foreach (var result in results) { result.番号 = dataList.Count + 1; dataList.Add(result); } })); } catch(Exception e) { Dispatcher.Invoke((Action)(() => { status.Content = targetFilename; var dataList = resultDataGrid.ItemsSource as ObservableCollection ; dataList.Add(new Result() { 番号 = dataList.Count + 1, 結果 = E_RESULT.失敗, ファイル名 = jsonFilename, エラーメッセージ = "タイムスタンプの取得に失敗しました。", 例外メッセージ = e.ToString() }); })); } } else { Dispatcher.Invoke((Action)(() => { status.Content = targetFilename; var dataList = resultDataGrid.ItemsSource as ObservableCollection ; dataList.Add(new Result() { 番号 = dataList.Count + 1, 結果 = E_RESULT.SKIP, ファイル名 = targetFilename, エラーメッセージ = "*.JSONファイルがありません。", 例外メッセージ = "" }); })); } } } }
<window contentrendered="Window_ContentRendered" height="450" mc:ignorable="d" title="Google Takeoutでダウンロードした画像のタイムスタンプをJSONファイルを参考に修正します。" width="1200" x:class="PhotosTimestamp.MainWindow" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:PhotosTimestamp" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> <dockpanel allowdrop="false" drop="ddPanel_Drop" name="ddPanel" previewdragover="ddPanel_PreviewDragOver"> <label dockpanel.dock="Bottom" name="status"> <datagrid dockpanel.dock="Top" isreadonly="True" name="resultDataGrid"> </datagrid></label></dockpanel> </window>
2022年11月14日月曜日
GoogleTakeoutでダウンロードしたGoogleフォト画像のファイルタイムスタンプを修正する
概要
①PowerShellのスクリプトを実行できるようにする
PowerShell 管理者権限として実行します。ウインドウズメニューを開いた状態で「PowerSell」と入力すると見つかるので、「管理者として実行する」を選択してください。
以下のコマンドを実行し現在の権限を確認しメモしておいてください。
Get-ExecutionPolicy
以下のコマンドを実行して権限を変更してください。
Set-ExecutionPolicy RemoteSigned
変更してよいか確認されるので Y キーを押してください。
なお、権限(RestrictedやRemoteSignedなどの)の詳細は以下を参照してください。
②スクリプトを作成
拡張子を表示した状態で「CorrectTimestamp.ps1」というファイルを作成しメモ帳で以下の様に編集してください。
$files = Get-ChildItem $PSScriptRoot -File -Recurse -Exclude *.json,*.html,*.ps1,*.zip foreach($i in $files){ Write-Host $i.FullName $jsondata = Get-Content -Path $i".json" | ConvertFrom-JSON $j = $i.FullName+".json" $UnixTime = $jsondata.photoTakenTime.timestamp.Trim('"') $UtcTime = ([DateTime]::Parse("1970/01/01 00:00:00")).addSeconds($UnixTime) $LocalTime= [TimeZoneInfo]::ConvertTimeFromUtc($UtcTime,[TimezoneInfo]::local) Set-ItemProperty $i -name CreationTime -value $LocalTime Set-ItemProperty $j -name CreationTime -value $LocalTime Set-ItemProperty $i -name LastWriteTime -value $LocalTime Set-ItemProperty $j -name LastWriteTime -value $LocalTime } Write-Host "Processing has finished. Please press any key. . ." -NoNewLine [Console]::ReadKey($true) > $null
JSON内にはいくつかの日時が記録されております、上記のスクリプトではphotoLastModifiedTimeを更新日時に設定していますが、photoTakenTimeを採用するなど好みに合わせて変更してください。更新日時もphotoTakenTimeにしています。
③Zipファイルの展開とスクリプトの配置
Takeout機能でダウンロードしたZIPファイルを全て展開し、展開されたフォルダに「CorrectTimestamp.ps1」をコピーしてください。
④スクリプトの実行
「CorrectTimestamp.ps1」ファイルを右クリックして「PowerShellで実行」を行うとタイムスタンプが修正されます。
その他
JSONファイルに記録されているデータを使ってファイル名を変えたりすることもできるが、PowerShellでなくWSH(Windows Script Host)などでも処理できると思います。
2022年10月9日日曜日
バッチファイル(*.bat)でファイルをゴミ箱に入れる
バッチファイルでファイルを直接削除するのは怖いのでゴミ箱を経由する
バッチファイルで直接ゴミ箱に入れることはできないので、WSH経由で削除します。
PowerShellを使う方法もありますが、セキュリティ的に使えない事が多いのでWSHを選択しました。
また、そもそも全てをWSHで実現すれば良いと思いますが、BATファイルの方が楽なケースも多いので結構役に立ちます。
@echo on CScript.exe trash.js c:\folder\test1.txt c:\folder\test2.txt
var args = WScript.Arguments; var sa = WScript.CreateObject("Shell.Application"); var fso = WScript.CreateObject("Scripting.FileSystemObject"); for (var i = 0; i < args.length; i++ ) { arg = args(i); WScript.Echo(arg); sa.NameSpace(10).MoveHere( arg ); while( fso.FileExists( arg ) ){ WScript.Sleep(100); } } // ゴミ箱の取得(NameSpace関数の10の意味) // https://learn.microsoft.com/ja-jp/windows/win32/api/shldisp/ne-shldisp-shellspecialfolderconstants // WScript.Echo( sa.NameSpace(10).Title );
今後改善したい項目
- エラー処理
- 相対パス指定対応
2022年8月28日日曜日
WSH(JScript)で*.xlsmを*.xlsxに変換する
マクロを有効にした XML ブック(*.xlsm)を既定のXML ブック(*.xlsx)に変換する。
xlsm2xlsx.jsのアイコンに変換したい *.xlsm ファイルを複数Drag&Dropすると *.xlsx ファイルを生成する。
xlsm2xlsx.js
var xlWorkbookDefault = 51; var ExcelApp = new ActiveXObject( "Excel.Application" ); // *.jsファイルにDrag&Dropされたファイルを順番に処理 var args = WScript.Arguments; for (var i = 0; i < args.length; i++ ) { var orgFilename = args(i); // 特定の拡張子のみ処理を実施 var orgExtension = orgFilename.split('.').pop(); if( orgExtension == 'xlsm' ){ var baseName = orgFilename.substring(0, orgFilename.indexOf('.'+orgExtension)) // ファイルを開いて形式を変更して保存 var book = ExcelApp.Workbooks.Open( orgFilename ); ExcelApp.DisplayAlerts= false; book.SaveAs( baseName + '.xlsx' , xlWorkbookDefault ); ExcelApp.DisplayAlerts= true; ExcelApp.Quit(); } } WScript.Echo( "finish" ); ExcelApp = null;
参考
https://docs.microsoft.com/ja-jp/office/vba/api/excel.xlfileformat
https://docs.microsoft.com/ja-jp/office/vba/api/excel.workbook.saveas
2022年7月3日日曜日
HTML+CSSでトグルボタン
すぐに忘れるので画像も文字列も変化するトグルボタン。デザインは適当。

まずはコードから
<!DOCTYPE html> <html> <head> </head> <body> <style> .btn,.btn:checked+label,.btn+label+label { display:none; } .btn+label,.btn:checked+label+label { display:inline-block; user-select: none; background-repeat: no-repeat; } .btn+label { background-color:aqua; } .btn:checked+label+label { background-color:pink; } .btn:active+label, .btn:active+label+label { background-color: lightgray; } .vertical+label,.vertical+label+label { padding-top: 2em; background-position:top; background-size: auto 2em; } .horizontal+label,.horizontal+label+label { padding-left: 2em; background-position:left; background-size: 2em auto; } .image+label,.image+label+label { height: 2em; width: 2em; background-position:center; background-size: contain; text-indent:100%; white-space:nowrap; overflow:hidden; } .text+label,.text+label+label { background-position:center; background-size: 0 0; } </style> <table> <TR><TH>ボタン</TH><TH>トグル</TH></TR> <TR><TD> <input class="btn vertical" type="button" id="1"/> <label for="1" style="background-image:url(send.png)">メール送信</label> <hr> <input class="btn horizontal" type="button" id="2"/> <label for="2" style="background-image:url(send.png)">メール送信</label> <hr> <input class="btn image" type="button" id="5"/> <label for="5" style="background-image:url(send.png)">メール送信</label> <hr> <input class="btn text" type="button" id="6"/> <label for="6" style="background-image:url(send.png)">メール送信</label> <hr> </TD> <TD> <input class="btn vertical" type="checkbox" id="3"/> <label for="3" style="background-image:url(opened.png)">既読メール</label> <label for="3" style="background-image:url(unopened.png)">未読メール</label> <hr> <input class="btn horizontal" type="checkbox" id="4" checked/> <label for="4" style="background-image:url(opened.png)">既読メール</label> <label for="4" style="background-image:url(unopened.png)">未読メール</label> <hr> <input class="btn image" type="checkbox" id="7"/> <label for="7" style="background-image:url(opened.png)">既読メール</label> <label for="7" style="background-image:url(unopened.png)">未読メール</label> <hr> <input class="btn text" type="checkbox" id="8"/> <label for="8" style="background-image:url(opened.png)">既読メール</label> <label for="8" style="background-image:url(unopened.png)">未読メール</label> <hr> </TD></TR> </table> </body> <html>
動作は以下のような感じ
普通のボタン | トグルするボタン |
---|---|
|
|
2022年5月28日土曜日
GASでドライブの画像を表示する
知恵袋への回答は質問に直接答えたが、画面ロード時に時間がかかりそうなので処理を全体的に変えてみた。
function doGet(e) { return HtmlService.createTemplateFromFile("index.html").evaluate().setTitle("タイトル"); } function GetBase64Image(fileId,param) { var file = DriveApp.getFileById(fileId); var blob = file.getBlob(); var contentType = blob.getContentType(); var base64 = Utilities.base64Encode(blob.getBytes()); var imageSrc = "data:" + contentType + ";base64, " + base64; return {"name":file.getName(),"id":fileId,"base64":imageSrc,"param":param}; }index.html
<html> <head> <script> <? var folder = DriveApp.getFolderById("1MdeTdaYDv9BK9QTaZes5_bTDRpKiLRnA"); var files = folder.getFiles(); var list = []; while (files.hasNext()) { var file = files.next(); list.push({"id":file.getId(),"name":file.getName(),"url":file.getUrl()}); } ?> var list = <?!= JSON.stringify(list) ?>; function onSelected(event) { var img = document.getElementById("image") img.alt = "Loading...."; img.src = null; var fileId = event.currentTarget.value; google.script.run .withSuccessHandler( SuccessGetBase64Image ) .withFailureHandler( Failure ) .GetBase64Image( fileId, img.id ); } function Failure(){ alert("失敗"); } function SuccessGetBase64Image(entry) { document.getElementById(entry.param).alt = entry.name; document.getElementById(entry.param).src = entry.base64; } function onLoad() { var select = document.getElementById("select"); select.addEventListener('change', onSelected); list.forEach( file => { var option = document.createElement("option"); option.value = file.id; option.innerText = file.name; select.appendChild(option); }); } </script> </head> <body onload="onLoad();"> <label for="select">Choose a menu:</label> <select name="select" id="select"> <option value="">--Please choose an option--</option> </select> <p><img src="" height="562" width="750" alt="選択されてません" align="top" id="image">ここに画像表示</p> </script> </body> </html>