ロック画面のスポットライト画像をコピーしてデスクトップにも流用させたい

ロック画面は何にしてますか?僕は設定でWindowsスポットライトにしてます。
これ、たぶんBingから定期的にダウンロードしてロック画面にスライドショー的に表示していると思うんだけど、なぜかログイン後のデスクトップの背景の設定でWindowsスポットライトの選択肢がないんですよねぇ。

Microsoft Storeで壁紙系のアプリを探せばたぶんあると思いますが、たぶんに要らん機能がくっついてきたり個人情報が抜かれたりと面倒なのでやっぱり自分でなんとかするのが基本です。そうです、信じられるのは唯一自分なのです(笑)

Windowsスポットライトで使用される画像ファイルは、
%LOCALAPPDATA%\Packages\Microsoft.Windows.ContentDeliveryManager_cw5n1h2txyewy\LocalState\Assets
に保存されているようです。拡張子がないので判別はできませんが、概ね400KB以上のファイルサイズのファイルが該当する画像で、任意のフォルダにコピーして拡張子.JPGでリネームすれば使いまわせます。ライセンスとか分からんが、どこぞに配布するわけではないので大丈夫でしょう。

定期的にピクチャフォルダにコピーすればいいんですが・・・いちいちフォルダ開いてファイルを選別してコピーしてリネームして・・・うわぁぁぁあーーーーーーん、超絶メンドクセーーーーーーー。
さらに、バックグラウンドでダウンロードされる画像ファイルには縦長と横長がごちゃまぜになっている。縦長の画像は不要なので、これも選別項目です。さらにメンドクサイ。

UNIX系のOSなら find コマンドやらimagemagickを駆使してシェルスクリプト化すればいいんですが・・・。
PowerShellを使えばできそうですけど、んーーーー、C#で組んだ方が早ぇよ。。。ということで、組んでみる。

ファイルサイズを判別するのは簡単で、JPEGファイルの高さと幅を取得するのがメンドクサそうですが・・・単純にSOFマーカーから取得することで手抜きします。完璧にしようとすると僕の知識では無理です。

/******************************************************************
  FileName : CopyWindowsSpotLight.cs

  使用方法:
    CopyWindowsSpotLight.exe コピー元 コピー先
  
  コピー元は 上記の %LOCALAPPDATA%..... を指定する。
 
  コピー元のディレクトリとコピー先のディレクトリを指定して次の条件でコピーする。
 1)ファイルサイズ400KB以上のファイルをJPEGファイルとしてみなす。
 (本来はファイルの先頭32ビットぐらいを読んでJPEGファイルかの判別をする必要がありますが・・・)
 2)ランドスケープ(幅が高さより大きい)
 3)既にコピーしたしたファイルはスキップする。
 4)コピーするとき 拡張子 .jpg を付けてコピーする

 どのタイミングでスポットライトの画像がダウンロードされるのか分からないので、
 実際にはタスクスケジューラに登録して定期的に実行させます。

  コピー元はハードコードしてもいいかも・・・。
******************************************************************/
using System;
using System.Text;
using System.IO;
using System.Linq;

namespace org.ptsv.console
{
  class CopySpotlight
  {
    // エントリポイント
    static int Main(string [] args)
    {
      if(args.Length < 2)
      {
        Console.Error.WriteLine("Usage: command <source directory> <destination directory>");
        return -1;
      }

      char[] trimChars = { '\\' , ' ' };
      var src = args[0].TrimEnd(trimChars);
      var dest = args[1].TrimEnd(trimChars);

      if(!Directory.Exists(src) || !Directory.Exists(dest))
        Console.Error.WriteLine("ディレクトリが存在しません。");

      // コピー元のディレクトリのファイル一覧を取得してフィルタリングしていきます。
      // 最終的に プロパティ s,d を持つ匿名クラスに変換して foreach で回していきます。
      // 本来はtry-catchで囲むべきですが・・・
      var enumerator = Directory
        .EnumerateFiles(src)
        .Where(f => (new FileInfo(f)).Length >= 400 * 1024)
        .Where(IsLandscape)
        .Select(f => new { s = f,d = dest + "\\" + Path.GetFileName(f) + ".jpg"});

      foreach(var set in enumerator)
      {
        try {
          File.Copy(set.s,set.d);
          Console.Error.WriteLine("done copied 1 file.");
        } catch (Exception e) {
          Console.Error.WriteLine("skip copy since file already exists.");
        }
      }

      return 0;
    }

  // 値をスワップする(マイクロソフトのドキュメントからコピペ )
  // https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/generics/generic-methods
    protected static void Swap<T>(ref T lhs, ref T rhs)
    {
      T temp;
      temp = lhs;
      lhs = rhs;
      rhs = temp;
    }

    // JPEGファイルの縦横サイズから 幅が高さより大きいものを選択できるようにする
    // BitConverterってリトルエンディアン(x86系なら殆どコレだと思うけど)しか対応してないみたいですねぇ。。。
    // SOFマーカーから取得できるサイズってビックエンディアンで記録されている?のでひと手間必要。ハマった。
    // Swapせずそのまま得られた16bit整数を System.Net.IPAddress.HostToNetworkOrder関数で変換するって方法もある。
    // どっちにしろメンドクセー。
    // 追記: 単純に 2バイトから16bitを合成する関数かけばよかった。
    // protected static int ConvertToUInt16(byte a,byte b)
    // {
    //    return (a << 8) | b;
    // }
    protected static bool IsLandscape(string f)
    {
      ushort width = 0,height = 0;
      using (FileStream fs = File.OpenRead(f))
      {
        int bufSize = 4096;
        byte[] content = new byte[bufSize];
        int readLen = 0;
        bool isReadBreak = false;

        // ファイル全てを読み込むのは効率悪いので細切れで読んでいきます。
        // 大抵は先頭に記述してあると思うので一回で済むと思います。
        // 追記:2020/05/04
        // これだとバッファ境界付近に SOFマーカーが現れたとき、index + 5,とかindex + 8とかのアクセスでエラーで落ちる。
        // 下記コードは不完全だと気付きました(遅)💦 やはり一旦すべてを読むか、
        // SOFマーカーが見つかったらその位置から再度fs.Readしなおすか、とかにする必要がある。時間あったらまた修正しよ。
        while(0 < (readLen = fs.Read(content,0,content.Length)))
        {
          for(int index = 0; index < content.Length; index++)
          {
            if (0xFF == content[index] && 0xC0 == content[index + 1])
            {
              // プラットフォームがリトルエンディアンの場合は、
              // 5バイト目と6バイト目、7バイト目と8バイト目をスワップする
              if(BitConverter.IsLittleEndian)
              {
                Swap<byte>(ref content[index + 5],ref content[index + 6]);
                Swap<byte>(ref content[index + 7],ref content[index + 8]);
              }
              height = BitConverter.ToUInt16(content,index + 5);
              width =  BitConverter.ToUInt16(content,index + 7);
              isReadBreak = true;
              break;
            }
          }
          if(isReadBreak)
            break;

          Array.Clear(content,0,readLen);
        }
        content = null;
      }
      return width > 0 && height > 0 && width >= height;
    }
  }
}

一応コンパイルした実行ファイルを置いときますが、自己責任でお願いします。いかなる理由での損害には応じられません。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください