Skip to content

Instantly share code, notes, and snippets.

@mystasly48
Last active February 25, 2021 15:26
Show Gist options
  • Save mystasly48/ccd40ca5379be82a768d5196a1345de1 to your computer and use it in GitHub Desktop.
Save mystasly48/ccd40ca5379be82a768d5196a1345de1 to your computer and use it in GitHub Desktop.
SaveWindowsWallpaper 2 - Windows10 のロック画面に表示されたことのある WindowsSpotlight の画像を保存するプログラム 修正版

SaveWindowsWallpaper 2

Windows10 のロック画面に表示されたのとのある WindowsSpotlight の壁紙を保存するプログラム。

SaveWindowsWallpaper の問題点を修正したバージョンになります。

使い方

  1. csc.exe などを用いて自力でコンパイルしてください。

  2. 適当な場所で実行します。コンソールが立ち上がり、処理が行われます。

  3. ピクチャにSaveWindowsWallpaperというフォルダができています。Horizontalは横長、Verticalは縦長です。

毎日実行することが好ましいので、自力でスタートアップフォルダなんかに入れてあげると良いでしょう。毎日増えていくと思います。

また、SaveWindowsWallpaper/Horizontalを壁紙のスライドショーのフォルダに登録しておくと最高です。間違えてもスタート画面のスライドショーにはしないようにしてくださいね。

出力の確認

検索対象のファイルが存在しなかった場合には、Files not found. と表示されます。

対象のファイルが存在した場合には、以下のログを出力しながら処理を行い、完了すると Completed. と表示されます。

  • Moved: Horizontal 横長画像のため Horizontal フォルダに移動しました。
  • Moved: Vertical 縦長画像のため Vertical フォルダに移動しました。
  • Deleted: Duplicate 既に過去にコピーをしているので削除しました。
  • Deleted: Too small 画像が 1920x1080 を満たさなかったため削除しました。(この場合、大体が意味不明なアイコンです)
  • Deleted: Not image 画像ではなかったため削除しました。

※SaveWindowsWallpaper では Moved の処理では元ファイルを残す「コピー」を実行していましたが、SaveWindowsWallpaper2 では元ファイルを残さない「切り取り(移動)」をしています。

また、 Deleted においても、元ファイルを残さない「削除」をしています。

関連フォルダ・ファイルについて

このプログラムは以下のフォルダ・ファイルにアクセスしています。

  • 元画像の保存フォルダです。移動や削除などを行います。 %LocalApplicationData%\Packages\Microsoft.Windows.ContentDeliveryManager_cw5n1h2txyewy\LocalState\Assets\

  • 横長の画像を保存します。
    %MyPictures%\SaveWindowsWallpaper\Horizontal\

  • 縦長の画像を保存します。
    %MyPictures%\SaveWindowsWallpaper\Vertical\

  • 画像重複防止用の設定ファイルを保存します。 %MyPictures%\SaveWindowsWallpaper\Settings.xml

もしも元画像の保存フォルダが存在しないようなエラーが発生した場合には、%LocalApplicationData%\Packages\ にアクセスしてそれっぽいフォルダを探してください・・・。

%LocalApplicationData%はユーザにより異なることがありますが、基本的には C:\Users\[username]\AppData\Local\です。

%MyPictures%もユーザにより異なることがありますが、基本的には C:\Users\[username]\Pictures\です。

Settings.xml は以前に移動した画像のハッシュ値を保存していますので、削除されますと保存画像に重複が生じてしまいます。

[username]はご自身のパソコンのユーザ名です。

変更点

  1. 同じ画像でも、ファイル名が違うことで違う画像だと認識され、保存してしまう。

  2. 違う画像でも、ファイル名が同じということで同じ画像だと認識され、スキップしてしまう。

SaveWindowsWWallpaper2 では上記の問題点を、画像のハッシュを取得して比較することで解決しました。

初回の処理では1分ほどかかってしまうかもしれませんが、2回目以降はすぐに終わるかと思います。

というのも、処理時間が1枚あたり0.2秒かかりますので、300枚ですと1分かかってしまうためです。

2回目以降は比較対象のファイルが減りますので、一瞬または数秒で終わります。

もしも以前に SaveWindowsWallpaper を使用されたことがありましたら、保存された画像ファイルをすべて検索対象のフォルダに入れていただくことで、現時点での重複ファイルをすべて削除できます。ぜひお試しください。

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Xml.Serialization;
public class Program {
// 設定ファイルに関する変数
private static Settings settings = new Settings();
private static string SettingsPath;
// 検索対象フォルダや保存作フォルダ等の変数
private const string DIRECTORY_UID = "cw5n1h2txyewy";
private static string SearchDirectory, SaveDirectory, HorizontalDirectory, VerticalDirectory;
private static string[] ImagePaths;
public static void Main() {
PrepareImagePaths();
if (ImagePaths.Length > 0) {
loadSettings();
JudgeAndDeal();
saveSettings();
Console.Write("Completed.");
} else {
Console.Write("Files not found.");
}
Console.WriteLine(" Press the any key to exit.");
Console.ReadKey();
}
// 検索対象フォルダの指定や、保存先フォルダの指定等
private static void PrepareImagePaths() {
SearchDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Packages\\Microsoft.Windows.ContentDeliveryManager_" + DIRECTORY_UID + "\\LocalState\\Assets");
SaveDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "SaveWindowsWallpaper");
HorizontalDirectory = Path.Combine(SaveDirectory, "Horizontal");
VerticalDirectory = Path.Combine(SaveDirectory, "Vertical");
ImagePaths = Directory.GetFiles(SearchDirectory);
SettingsPath = Path.Combine(SaveDirectory, "Settings.xml");
if (!Directory.Exists(SaveDirectory)) {
Directory.CreateDirectory(SaveDirectory);
}
if (!Directory.Exists(HorizontalDirectory)) {
Directory.CreateDirectory(HorizontalDirectory);
}
if (!Directory.Exists(VerticalDirectory)) {
Directory.CreateDirectory(VerticalDirectory);
}
}
// #####このメソッド、もっと一般化できると思うよ#####
// 画像の状態(内容等)を確認して、それに対応した処理を行う
private static void JudgeAndDeal() {
// 新規ファイルの全探索
foreach (string file in ImagePaths) {
// フォルダ名を除いて、ファイル名のみ取得する
string fileName = file.Substring(SearchDirectory.Length + 2);
string saveFilePath = "";
try {
using (Image image = Image.FromFile(file)) {
if (isHighDefinitionImage(image.Width, image.Height)) {
string hash = getHash(file);
if (settings.Hashes.Contains(hash)) {
Console.WriteLine("Deleted: Duplicate");
} else {
if (isHorizontalImage(image.Width, image.Height)) {
saveFilePath = getOptimumPath(HorizontalDirectory, fileName);
Console.WriteLine("Moved: Horizontal");
} else if (isVerticalImage(image.Width, image.Height)) {
saveFilePath = getOptimumPath(VerticalDirectory, fileName);
Console.WriteLine("Moved: Vertical");
} else {
Console.WriteLine("Unknown error! Path: " + file);
continue;
}
settings.Hashes.Add(hash);
}
} else {
Console.WriteLine("Deleted: Too small");
}
}
} catch (OutOfMemoryException) {
Console.WriteLine("Deleted: Not image");
}
if (saveFilePath != "") {
File.Move(file, saveFilePath);
} else {
File.Delete(file);
}
}
}
private static bool isHighDefinitionImage(int width, int height) =>
(isHorizontalImage(width, height) || isVerticalImage(width, height));
private static bool isHorizontalImage(int width, int height) =>
(width >= 1920 && height >= 1080);
private static bool isVerticalImage(int width, int height) =>
(width >= 1080 && height >= 1920);
// 画像のハッシュ値を取得
private static string getHash(string file) {
Bitmap img = new Bitmap(file);
BitmapData bd = img.LockBits(new Rectangle(0, 0, img.Width, img.Height), ImageLockMode.ReadOnly, img.PixelFormat);
try {
int bsize = bd.Stride * img.Height;
byte[] bytes = new byte[bsize];
Marshal.Copy(bd.Scan0, bytes, 0, bsize);
MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
byte[] hash = md5.ComputeHash(bytes);
return string.Join("", hash.Select(b => b.ToString()).ToArray());
} finally {
img.UnlockBits(bd);
img.Dispose();
}
}
// 保存に最適な名前を取得(重複していなければそのまま返却、重複していれば生成して返却)
private static string getOptimumPath(string directory, string name) {
string path = Path.Combine(directory, name + ".jpeg");
while (true) {
if (File.Exists(path)) {
path = Path.Combine(directory, getRandomCode() + ".jpeg");
} else {
return path;
}
}
}
// 適当な文字列を返却
private static string getRandomCode() {
var result = "";
var codeChar = "0123456789abcdefghijklmnopqrstuvwxyz";
var rand = new Random();
for (var i = 0; i < 64; i++) {
var pos = rand.Next(codeChar.Length);
var code = codeChar[pos];
result += code;
}
return result;
}
private static void loadSettings() {
if (File.Exists(SettingsPath)) {
var serializer = new XmlSerializer(typeof(Settings));
var reader = new StreamReader(SettingsPath);
settings = (Settings)serializer.Deserialize(reader);
reader.Close();
} else {
saveSettings();
loadSettings();
}
}
private static void saveSettings() {
var serializer = new XmlSerializer(typeof(Settings));
var writer = new StreamWriter(SettingsPath, false, Encoding.UTF8);
serializer.Serialize(writer, settings);
writer.Close();
}
}
public class Settings {
public List<string> Hashes = new List<string>();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment