例としてxkcdを使用してC#を使用してWebコミックからPDFブックを作成する

Sony PRS-650およびxkcd 新しいxkcdのリリースを考慮して、購入したばかりのSony PRS-650電子ブックを見て、すぐに考えました-私はその上でコミックを見たいです! Xkcdは白黒で、通常サイズは小さいです。 わずかにグーグル、TPB上の画像のコレクション、およびPDFを実行するbash上のスクリプトのみが見つかりました。 私はプログラミングを少し楽しんで、私のお気に入りのC#でコミックグラバーを作成することにしました。



コンソールアプリケーションを使用することも可能ですが、わかりやすくするために、WPFで簡単なインターフェイスを作成しました。





コードの完全な分析は冗長になるため、主なポイントについて説明します。 Google Codeから完全なアプリケーションコードをすぐに開く/ダウンロードすることをお勧めします











1.サイトから写真、タイトル、代替テキストを取得する



xkcdでは、漫画はxkcd.com/nの形式のアドレスに便利に配置されます(n = 1 ...)。

最初の考えは、ページコードから適切なものを削除することでしたが、 xkcd.com {0} /info.0.jsonの形式のアドレスでJSONのすべての情報を取得できることが判明しました



.NETのJSONには、DataContractJsonSerializerがあります

適切なDataContractを作成します。



[DataContract] public class XkcdComic { #region Public properties and indexers [DataMember] public string img { get; set; } [DataMember] public string title { get; set; } [DataMember] public string month { get; set; } [DataMember] public string num { get; set; } [DataMember] public string link { get; set; } [DataMember] public string year { get; set; } [DataMember] public string news { get; set; } [DataMember] public string safe_title { get; set; } [DataMember] public string transcript { get; set; } [DataMember] public string day { get; set; } [DataMember] public string alt { get; set; } #endregion }
      
      







...および使用:

  private static XkcdComic GetComic(string url) { var stream = new WebClient().OpenRead(url); if (stream == null) return null; var serializer = new DataContractJsonSerializer(typeof (XkcdComic)); return serializer.ReadObject(stream) as XkcdComic; }
      
      







xkcd.com/info.0.jsonでは 、最新のコミックブックを入手でき、numフィールドからその番号を取得して、その総数を調べます。

画像自体を収縮させるために残っています、すべてが簡単です:

 var imageBytes = WebRequest.Create(comicInfo.img).GetResponse().GetResponseStream().ToBytes();
      
      





...ここでcomicInfoはJSONからのデータであり、ToBytes()はデータをストリームから配列に読み込む単純な拡張メソッドです。



コミックストリップ(コミックストリップ、または単数形で正しく呼び出す方法)を表すには、Comicクラスが使用されます。 画像の受信バイトを検証するために(そして何か間違ったものをダウンロードしたり、サーバーがエラーを返すなど)、クラスコンストラクターをプライベートにし、ファクトリーCreateメソッドを追加しました。これはデコードエラーの場合にnullを返します。 BitmapImageはデコードに使用され、成功すると、結果をプレビューするためのサムネイルとして使用されます。

  public static Comic Create(byte[] imageBytes) { try { // Validate image bytes by trying to create a Thumbnail. return new Comic {ImageBytes = imageBytes}; } catch { // Failure, cannot decode bytes return null; } } public byte[] ImageBytes { get { return _imageBytes; } private set { _imageBytes = value; var bmp = new BitmapImage(); bmp.BeginInit(); bmp.DecodePixelHeight = 100; // Do not store whole picture bmp.StreamSource = new MemoryStream(_imageBytes); bmp.EndInit(); bmp.Freeze(); Thumbnail = bmp; } }
      
      







すべてをまとめると、コメディアンストリップをその番号でダウンロードする方法が得られます。

  protected override Comic GetComicByIndex(int index) { // Download comic JSON var comicInfo = GetComic(string.Format(UrlFormatString, index + 1)); if (comicInfo == null) return null; // Download picture var imageStream = WebRequest.Create(comicInfo.img).GetResponse().GetResponseStream().ToMemoryStream(); var comic = Comic.Create(imageStream.GetBuffer()); if (comic == null) return null; comic.Description = comicInfo.alt; comic.Url = comicInfo.link; comic.Index = index + 1; comic.Title = comicInfo.title; // Auto-rotate for best fit var t = comic.Thumbnail; if (t.Width > t.Height) { comic.RotationDegrees = 90; } return comic; }
      
      







そのため、多数のコミックと、インデックスでストリップを取得する方法があります。



ダウンロードの並列化



長い間試していたので、 Task Parallel Libraryを使用しますが、理由はありませんでした。 一見、すべてが単純で、ループでGetComicByIndex(i)を直接呼び出す代わりに、var task = Task.Factory.StartNew(()=> GetComicByIndex(i))を実行します。 実行中のすべてのタスクをタスク配列に書き込み、Task.WaitAll(tasks)を実行します。その後、task.Resultから各タスクの結果を取得します。 しかし、このアプローチでは、進行状況を追跡して、既にロードされているストリップをユーザーに表示することはできません。 この問題を解決するには、WaitAnyを使用してreturnを生成し、完了後すぐに各タスクの結果を返します。

  public IEnumerable<Comic> GetComics() { var count = GetCount(); var tasks = Enumerable.Range(0, count).Select(GetTask).ToList(); while (tasks.Count > 0) // Iterate until all tasks complete { var task = tasks.WaitAnyAndPop(); if (task.Result != null) yield return task.Result; } }
      
      







ここでは、GetTaskメソッドはGetComicByIndex(i)タスクに加えて、エラー処理とキャッシュを返します(これはこの記事の範囲外です)。 WaitAnyAndPopは、タスクの1つが完了するのを待ってリストから削除し、以下を返す拡張メソッドです。

 WaitAnyAndPop — extension ,      ,      : public static Task<T> WaitAnyAndPop<T>(this List<Task<T>> taskList) { var array = taskList.ToArray(); var task = array[Task.WaitAny(array)]; taskList.Remove(task); return task; }
      
      







現在、ViewModelコード(この記事ではアーキテクチャ上の問題を考慮していませんが、MVVM(Model-View-ViewModel)はWPFアプリケーションの事実上の標準であり、もちろん、収縮、エクスポートなどのコードは対応するクラスに分類されています) GetComicsメソッドの結果に応じてバックグラウンドストリームを反復処理し、ユーザーストリップを到着時に表示できます。

  private readonly Dispatcher _dispatcher; private readonly ObservableCollection<Comic> _comics = new ObservableCollection<Comic>(); private void StartGrabbing() { _dispatcher = Dispatcher.CurrentDispatcher; // ObservableCollection modifications should be performed on the UI thread ThreadPool.QueueUserWorkItem(o => DoGrabbing()); } private void DoGrabbing() { var grabber = new XkcdGrabber(); foreach (var comic in grabber.GetComics()) { var c = comic; _dispatcher.Invoke((Action) (() => Comics.Add( c )), DispatcherPriority.ApplicationIdle); } }
      
      







2. WPFでコミックを表示する



XAMLコードでは、ObservableCollectionへのバインドのみを行い、対応するDataTemplateを準備して、ツールチップの代替テキストを使用して、読み込みプロセスとコミック自体を監視します。

  <ListView ItemsSource="{Binding Comics}" ScrollViewer.VerticalScrollBarVisibility="Disabled" x:Name="list" Margin="5,0,5,0" ScrollViewer.HorizontalScrollBarVisibility="Visible" Grid.Row="1"> <ItemsControl.ItemTemplate> <DataTemplate> <Border BorderBrush="Gray" CornerRadius="5" Padding="5" Margin="5" BorderThickness="1"> <StackPanel Orientation="Vertical"> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding Index}" FontWeight="Bold" /> <TextBlock Text="{Binding Title}" FontWeight="Bold" Margin="10,0,0,0" /> </StackPanel> <Image Source="{Binding Thumbnail}" ToolTip="{Binding Description}" Height="{Binding Thumbnail.PixelHeight}" Width="{Binding Thumbnail.PixelWidth}" /> </StackPanel> </Border> </DataTemplate> </ItemsControl.ItemTemplate> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <StackPanel Orientation="Horizontal" /> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> </ListView>
      
      







3. PDFブックを作成する



PDFが選ばれたのは、その人気とソニーの電子書籍での優れたサポートのためです。 .NETでPDFを使用するには、便利なオープンソースライブラリiTextSharpがあります(プロジェクトをビルドするには、個別にダウンロードする必要があります)。 ここではすべてが非常に簡単です。 例外処理を省略し、画像サイズとフォントを調整すると、次の結果が得られます。

 var document = new Document(PageSize.LETTER); var wri = PdfWriter.GetInstance(document, new FileStream(fileName, FileMode.Create)); document.Open(); foreach (var comic in comics.OrderBy(c => c.Index).ToList()) { var image = Image.GetInstance(new MemoryStream(comic.ImageBytes)); var title = new Paragraph(comic.Index + ". " + comic.Title, titleFont); title.SetAlignment("Center"); document.Add(title); document.Add(image); document.Add(new Phrase(comic.Description, altFont)); document.Add(Chunk.NEXTPAGE); } document.Close();
      
      







結果



その結果、PDFへのエクスポートに加えて、コミックを便利に表示できるアプリケーションが作成されます。



Webcomic Grabberスクリーンショット



結果が本でどのように見えるかは、記事の最初の写真で見ることができます。



記事に残されているもの



アプリケーションの起動間でダウンロードされたデータをキャッシュする(IsolatedStorageを使用して行われます)。

他のWebコミックのサポート(この目的のために、IGrabberインターフェイスを事前に割り当て、TaskParallelGrabberの機能の一部をレンダリングしました。記事の執筆中に、WhatTheDuckとCyanide&Happinessのグラバーを追加しました)。



参照資料



アプリケーションコード(C#): Googleコード

.NETでPDFを操作する: iTextSharp

コミック: xkcd



UPD:

結果のPDFコンパイルされたプログラム をアップロードしてくれたXHunterに感謝します!



UPD2:

ここでは、WCFを使用したコミックのポンピングのトピックに関する詳細な「応答」記事へのリンクを残します。http//darren-brown.com/?p = 37



All Articles