From b63689e7dc10588e418ea52edb96ada4086277ac Mon Sep 17 00:00:00 2001 From: spx Date: Fri, 6 Feb 2015 23:57:36 +0900 Subject: [PATCH] =?utf8?q?=E6=96=B0=E3=81=97=E3=81=84=E3=83=A1=E3=83=87?= =?utf8?q?=E3=82=A3=E3=82=A2=E3=82=BF=E3=82=A4=E3=83=97=20"animated=5Fgif"?= =?utf8?q?,=20"video"=20=E3=81=AB=E4=BB=AE=E5=AF=BE=E5=BF=9C=20=E3=81=A9?= =?utf8?q?=E3=81=A1=E3=82=89=E3=82=82=E3=82=B5=E3=83=A0=E3=83=8D=E3=82=A4?= =?utf8?q?=E3=83=AB=E6=9E=A0=E3=81=AB=E5=86=8D=E7=94=9F=E3=83=9C=E3=82=BF?= =?utf8?q?=E3=83=B3=E3=82=92=E6=8F=8F=E7=94=BB=E3=81=97=E3=81=A6=E8=A1=A8?= =?utf8?q?=E7=A4=BA=E3=81=99=E3=82=8B=E3=81=A0=E3=81=91=E3=80=82=E5=86=8D?= =?utf8?q?=E7=94=9F=EF=BC=88=E3=83=80=E3=83=96=E3=83=AB=E3=82=AF=E3=83=AA?= =?utf8?q?=E3=83=83=E3=82=AF=EF=BC=89=E3=81=AFmp4=E3=81=AEURL=E3=82=92?= =?utf8?q?=E3=83=96=E3=83=A9=E3=82=A6=E3=82=B6=E3=81=AB=E4=B8=B8=E6=8A=95?= =?utf8?q?=E3=81=92=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- OpenTween.Tests/TweetThumbnailTest.cs | 26 +++++------ OpenTween/Api/TwitterEntity.cs | 29 +++++++++++++ OpenTween/OTPictureBox.cs | 22 ++++++++++ OpenTween/OpenTween.csproj | 2 + OpenTween/Properties/Resources.Designer.cs | 32 +++++++++----- OpenTween/Properties/Resources.resx | 3 ++ OpenTween/Resources/PlayableOverlayImage.png | Bin 0 -> 5166 bytes OpenTween/StatusDictionary.cs | 42 ++++++++++++++++-- OpenTween/Thumbnail/Services/TwitterComVideo.cs | 55 ++++++++++++++++++++++++ OpenTween/Thumbnail/ThumbnailGenerator.cs | 18 ++++---- OpenTween/Thumbnail/ThumbnailInfo.cs | 10 ++++- OpenTween/Tween.cs | 11 +++++ OpenTween/Twitter.cs | 24 ++++++++--- 13 files changed, 231 insertions(+), 43 deletions(-) create mode 100644 OpenTween/Resources/PlayableOverlayImage.png create mode 100644 OpenTween/Thumbnail/Services/TwitterComVideo.cs diff --git a/OpenTween.Tests/TweetThumbnailTest.cs b/OpenTween.Tests/TweetThumbnailTest.cs index ef41a253..433a84a9 100644 --- a/OpenTween.Tests/TweetThumbnailTest.cs +++ b/OpenTween.Tests/TweetThumbnailTest.cs @@ -127,9 +127,9 @@ namespace OpenTween var post = new PostClass { TextFromApi = "てすと http://foo.example.com/abcd", - Media = new List + Media = new List { - "http://foo.example.com/abcd", + new MediaInfo("http://foo.example.com/abcd"), }, }; @@ -183,9 +183,9 @@ namespace OpenTween var post = new PostClass { TextFromApi = "てすと http://foo.example.com/abcd", - Media = new List + Media = new List { - "http://foo.example.com/abcd", + new MediaInfo("http://foo.example.com/abcd"), }, }; @@ -216,10 +216,10 @@ namespace OpenTween var post = new PostClass { TextFromApi = "てすと http://foo.example.com/abcd http://bar.example.com/efgh", - Media = new List + Media = new List { - "http://foo.example.com/abcd", - "http://bar.example.com/efgh", + new MediaInfo("http://foo.example.com/abcd"), + new MediaInfo("http://bar.example.com/efgh"), }, }; @@ -266,7 +266,7 @@ namespace OpenTween var post = new PostClass { TextFromApi = "てすと", - Media = new List + Media = new List { }, }; @@ -280,9 +280,9 @@ namespace OpenTween var post2 = new PostClass { TextFromApi = "てすと http://foo.example.com/abcd", - Media = new List + Media = new List { - "http://foo.example.com/abcd", + new MediaInfo("http://foo.example.com/abcd"), }, }; eventCalled = false; @@ -298,10 +298,10 @@ namespace OpenTween var post = new PostClass { TextFromApi = "てすと http://foo.example.com/abcd http://foo.example.com/efgh", - Media = new List + Media = new List { - "http://foo.example.com/abcd", - "http://foo.example.com/efgh", + new MediaInfo("http://foo.example.com/abcd"), + new MediaInfo("http://foo.example.com/efgh"), }, }; diff --git a/OpenTween/Api/TwitterEntity.cs b/OpenTween/Api/TwitterEntity.cs index 54585b5d..863f4afc 100644 --- a/OpenTween/Api/TwitterEntity.cs +++ b/OpenTween/Api/TwitterEntity.cs @@ -86,6 +86,9 @@ namespace OpenTween.Api [DataMember(Name = "type")] public string Type { get; set; } + + [DataMember(Name = "video_info", IsRequired = false)] + public TwitterMediaVideoInfo VideoInfo { get; set; } } [DataContract] @@ -118,6 +121,32 @@ namespace OpenTween.Api } [DataContract] + public class TwitterMediaVideoInfo + { + [DataMember(Name = "aspect_ratio")] + public int[] AspectRatio { get; set; } + + [DataMember(Name = "duration_millis", IsRequired = false)] + public long? DurationMillis { get; set; } + + [DataMember(Name = "variants")] + public TwitterMediaVideoInfo.Variant[] Variants { get; set; } + + [DataContract] + public class Variant + { + [DataMember(Name = "bitrate")] + public int? Bitrate { get; set; } + + [DataMember(Name = "content_type")] + public string ContentType { get; set; } + + [DataMember(Name = "url")] + public string Url { get; set; } + } + } + + [DataContract] public class TwitterEntityUrl : TwitterEntity { [DataMember(Name = "display_url")] diff --git a/OpenTween/OTPictureBox.cs b/OpenTween/OTPictureBox.cs index 242c503d..eb511394 100644 --- a/OpenTween/OTPictureBox.cs +++ b/OpenTween/OTPictureBox.cs @@ -32,6 +32,7 @@ using System.Threading; using System.Net; using System.Net.Http; using System.IO; +using OpenTween.Thumbnail; namespace OpenTween { @@ -121,6 +122,9 @@ namespace OpenTween try { base.OnPaint(pe); + + // 動画なら再生ボタンを上から描画 + DrawPlayableMark(pe); } catch (ExternalException) { @@ -130,6 +134,24 @@ namespace OpenTween } } + private void DrawPlayableMark(PaintEventArgs pe) + { + var thumb = this.Tag as ThumbnailInfo; + if (thumb == null || !thumb.IsPlayable) return; + if (base.Image == base.InitialImage || base.Image == base.ErrorImage) return; + + var overlayImage = Properties.Resources.PlayableOverlayImage; + + var overlaySize = Math.Min(this.Width, this.Height) / 4; + var destRect = new Rectangle( + (this.Width - overlaySize) / 2, + (this.Height - overlaySize) / 2, + overlaySize, + overlaySize); + + pe.Graphics.DrawImage(overlayImage, destRect, 0, 0, overlayImage.Width, overlayImage.Height, GraphicsUnit.Pixel); + } + [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] diff --git a/OpenTween/OpenTween.csproj b/OpenTween/OpenTween.csproj index 07b1fcd2..440c2847 100644 --- a/OpenTween/OpenTween.csproj +++ b/OpenTween/OpenTween.csproj @@ -299,6 +299,7 @@ + @@ -770,6 +771,7 @@ + diff --git a/OpenTween/Properties/Resources.Designer.cs b/OpenTween/Properties/Resources.Designer.cs index 4c19b6df..35243f72 100644 --- a/OpenTween/Properties/Resources.Designer.cs +++ b/OpenTween/Properties/Resources.Designer.cs @@ -635,17 +635,17 @@ namespace OpenTween.Properties { /// 更新履歴 /// ///==== Ver 1.2.5-beta1(20xx/xx/xx) - /// - ///==== Ver 1.2.4(2014/11/23) - /// * NEW: Mobypicture (mobypicture.com) への画像投稿に対応 - /// * NEW: tmblr.co で短縮されたTumblrのURLに対してもサムネイル表示できるようにしました - /// * NEW: i.instagram.com ドメインのURLのサムネイル表示に対応 - /// * CHG: サービス終了のため、Twitpic を画像の投稿先から削除 - /// - それに伴い、デフォルトの投稿先を Twitter (pic.twitter.com) に変更しました - /// * CHG: プロフィール表示・編集でのエスケープ処理を twitter.com での挙動に合わせました - /// - 名前および現在地に < > " の記号を使うことはできません (Twitter側で削除されます) - /// - 自己紹介では < > " の記号はそれぞれ &lt; &gt; &quot; に変換した上で設定されます - /// * CHG: プロフィール画面へのD&Dによるアイコ [残りの文字列は切り詰められました]"; に類似しているローカライズされた文字列を検索します。 + /// * NEW: 他人にRTされた自分のツイートをメニューから削除できるようになりました + /// * NEW: UserStreams の favorited_retweet イベント追加に対応 + /// - イベント通知の有無は favorite イベントの通知設定に準じます + /// * NEW: 着せ替えアイコン機能についての説明テキストを同梱しました + /// * NEW: 短縮URLとして展開の対象とするドメインを追加しました + /// * CHG: 着せ替えアイコン機能で従来より省略可能なアイコンを増やしました + /// * CHG: 投稿欄のテキストボックスが一行表示の状態の時に下に余白が生じていた不具合を修正 + /// * CHG: API使用情報やステータスバー上のリクエスト残回数の表示方法を変更しました + /// - タブを切り替えるごとに、そのタブが使用しているAPIの残回数を表示するようにしました + /// - 注:UserStreamsで動作している一部のタブは残数更新が行われないため、表記が ??? になることがありますが、不具合ではありません + /// [残りの文字列は切り詰められました]"; に類似しているローカライズされた文字列を検索します。 /// internal static string ChangeLog { get { @@ -1786,6 +1786,16 @@ namespace OpenTween.Properties { } /// + /// 型 System.Drawing.Bitmap のローカライズされたリソースを検索します。 + /// + internal static System.Drawing.Bitmap PlayableOverlayImage { + get { + object obj = ResourceManager.GetObject("PlayableOverlayImage", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// /// 編集されていないため、公式RTとして投稿します。 よろしいですか?{0}[はい:公式RTで投稿]{0}[いいえ:非公式RTで投稿]{0}[キャンセル:投稿しない] に類似しているローカライズされた文字列を検索します。 /// internal static string PostButton_Click1 { diff --git a/OpenTween/Properties/Resources.resx b/OpenTween/Properties/Resources.resx index a0f38a2b..18b66a85 100644 --- a/OpenTween/Properties/Resources.resx +++ b/OpenTween/Properties/Resources.resx @@ -1180,4 +1180,7 @@ アイコンを変更します。よろしいですか? + + ..\Resources\PlayableOverlayImage.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + \ No newline at end of file diff --git a/OpenTween/Resources/PlayableOverlayImage.png b/OpenTween/Resources/PlayableOverlayImage.png new file mode 100644 index 0000000000000000000000000000000000000000..d1944a5bc3d21b26cd2393fadac914b0a9710658 GIT binary patch literal 5166 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 cs@;6>|3`cdE3Y!pH!00000 literal 0 HcmV?d00001 diff --git a/OpenTween/StatusDictionary.cs b/OpenTween/StatusDictionary.cs index 4ab5b3a4..215d8705 100644 --- a/OpenTween/StatusDictionary.cs +++ b/OpenTween/StatusDictionary.cs @@ -86,7 +86,7 @@ namespace OpenTween public int RetweetedCount { get; set; } public long? RetweetedByUserId { get; set; } public long? InReplyToUserId { get; set; } - public List Media { get; set; } + public List Media { get; set; } public string RelTabName { get; set; } public int FavoritedCount { get; set; } @@ -163,7 +163,7 @@ namespace OpenTween { RetweetedBy = ""; RelTabName = ""; - Media = new List(); + Media = new List(); ReplyToList = new List(); } @@ -400,13 +400,49 @@ namespace OpenTween var clone = (PostClass)this.MemberwiseClone(); clone.ReplyToList = new List(this.ReplyToList); clone.PostGeo = new StatusGeo { Lng = this.PostGeo.Lng, Lat = this.PostGeo.Lat }; - clone.Media = new List(this.Media); + clone.Media = new List(this.Media); return clone; } #endregion } + public class MediaInfo + { + public string Url { get; private set; } + public string VideoUrl { get; private set; } + + public MediaInfo(string url) + : this(url, null) + { + } + + public MediaInfo(string url, string videoUrl) + { + this.Url = url; + this.VideoUrl = !string.IsNullOrEmpty(videoUrl) ? videoUrl : null; + } + + public override bool Equals(object obj) + { + var info = obj as MediaInfo; + return info != null && + info.Url == this.Url && + info.VideoUrl == this.VideoUrl; + } + + public override int GetHashCode() + { + return (this.Url == null ? 0 : this.Url.GetHashCode()) ^ + (this.VideoUrl == null ? 0 : this.VideoUrl.GetHashCode()); + } + + public override string ToString() + { + return this.Url; + } + } + public sealed class TabInformations { //個別タブの情報をDictionaryで保持 diff --git a/OpenTween/Thumbnail/Services/TwitterComVideo.cs b/OpenTween/Thumbnail/Services/TwitterComVideo.cs new file mode 100644 index 00000000..9e97c423 --- /dev/null +++ b/OpenTween/Thumbnail/Services/TwitterComVideo.cs @@ -0,0 +1,55 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2015 spx (@5px) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; + +namespace OpenTween.Thumbnail.Services +{ + /// + /// サムネイル用URLと動画用URLを用意する + /// + class TwitterComVideo : IThumbnailService + { + public override Task GetThumbnailInfoAsync(string url, PostClass post, CancellationToken token) + { + return Task.Run(() => + { + var info = post.Media.FirstOrDefault(x => x.Url == url); + if (info.VideoUrl == null) return null; + + return new ThumbnailInfo + { + ImageUrl = info.VideoUrl, + ThumbnailUrl = url, + FullSizeImageUrl = url, + IsPlayable = true, + }; + }, token); + } + } +} diff --git a/OpenTween/Thumbnail/ThumbnailGenerator.cs b/OpenTween/Thumbnail/ThumbnailGenerator.cs index 66b0f6df..e6caae9b 100644 --- a/OpenTween/Thumbnail/ThumbnailGenerator.cs +++ b/OpenTween/Thumbnail/ThumbnailGenerator.cs @@ -51,6 +51,9 @@ namespace OpenTween.Thumbnail // ton.twitter.com new TonTwitterCom(), + // twitter.com/tweet_video + new TwitterComVideo(), + // DirectLink new SimpleThumbnailService(@"^https?://.*(\.jpg|\.jpeg|\.gif|\.png|\.bmp)$", "${0}"), @@ -211,18 +214,15 @@ namespace OpenTween.Thumbnail { var thumbnails = new List(); - if (post.Media != null) + foreach (var media in post.Media) { - foreach (var media in post.Media) - { - var thumbInfo = await ThumbnailGenerator.GetThumbnailInfoAsync(media, post, token) - .ConfigureAwait(false); + var thumbInfo = await ThumbnailGenerator.GetThumbnailInfoAsync(media.Url, post, token) + .ConfigureAwait(false); - if (thumbInfo != null) - thumbnails.Add(thumbInfo); + if (thumbInfo != null) + thumbnails.Add(thumbInfo); - token.ThrowIfCancellationRequested(); - } + token.ThrowIfCancellationRequested(); } if (post.PostGeo != null && !(post.PostGeo.Lat == 0 && post.PostGeo.Lng == 0)) diff --git a/OpenTween/Thumbnail/ThumbnailInfo.cs b/OpenTween/Thumbnail/ThumbnailInfo.cs index 9fee07ed..1f028741 100644 --- a/OpenTween/Thumbnail/ThumbnailInfo.cs +++ b/OpenTween/Thumbnail/ThumbnailInfo.cs @@ -37,6 +37,13 @@ namespace OpenTween.Thumbnail public string TooltipText { get; set; } public string FullSizeImageUrl { get; set; } + public bool IsPlayable + { + get { return _IsPlayable; } + set { _IsPlayable = value; } + } + private bool _IsPlayable = false; + public Task LoadThumbnailImageAsync() { return this.LoadThumbnailImageAsync(CancellationToken.None); @@ -72,7 +79,8 @@ namespace OpenTween.Thumbnail other.ImageUrl == this.ImageUrl && other.ThumbnailUrl == this.ThumbnailUrl && other.TooltipText == this.TooltipText && - other.FullSizeImageUrl == this.FullSizeImageUrl; + other.FullSizeImageUrl == this.FullSizeImageUrl && + other.IsPlayable == this.IsPlayable; } public override int GetHashCode() diff --git a/OpenTween/Tween.cs b/OpenTween/Tween.cs index 80b3bb7e..baef5a18 100644 --- a/OpenTween/Tween.cs +++ b/OpenTween/Tween.cs @@ -6080,6 +6080,17 @@ namespace OpenTween sb.AppendFormat("RetweetedBy : {0}
", _curPost.RetweetedBy); sb.AppendFormat("RetweetedId : {0}
", _curPost.RetweetedId); sb.AppendFormat("SearchTabName : {0}
", _curPost.RelTabName); + + sb.AppendFormat("Media.Count : {0}
", _curPost.Media.Count); + if (_curPost.Media.Count > 0) + { + for (int i = 0; i < _curPost.Media.Count; i++) + { + var info = _curPost.Media[i]; + sb.AppendFormat("Media[{0}].Url : {1}
", i, info.Url); + sb.AppendFormat("Media[{0}].VideoUrl : {1}
", i, info.VideoUrl ?? "---"); + } + } sb.Append("-----End PostClass Dump
"); PostBrowser.DocumentText = detailHtmlFormatHeader + sb.ToString() + detailHtmlFormatFooter; diff --git a/OpenTween/Twitter.cs b/OpenTween/Twitter.cs index fc8295b8..08872381 100644 --- a/OpenTween/Twitter.cs +++ b/OpenTween/Twitter.cs @@ -2981,7 +2981,7 @@ namespace OpenTween return retStr; } - public async Task CreateHtmlAnchorAsync(string text, List AtList, TwitterEntities entities, List media) + public async Task CreateHtmlAnchorAsync(string text, List AtList, TwitterEntities entities, List media) { if (entities != null) { @@ -2992,8 +2992,8 @@ namespace OpenTween ent.ExpandedUrl = await ShortUrl.Instance.ExpandUrlAsync(ent.ExpandedUrl) .ConfigureAwait(false); - if (media != null && !media.Contains(ent.ExpandedUrl)) - media.Add(ent.ExpandedUrl); + if (media != null && !media.Any(info => info.Url == ent.ExpandedUrl)) + media.Add(new MediaInfo(ent.ExpandedUrl)); } } if (entities.Hashtags != null) @@ -3018,8 +3018,20 @@ namespace OpenTween { foreach (var ent in entities.Media) { - if (!media.Contains(ent.MediaUrl)) - media.Add(ent.MediaUrl); + if (!media.Any(x => x.Url == ent.MediaUrl)) + { + if (ent.VideoInfo != null && + ent.Type == "animated_gif" || ent.Type == "video") + { + var videoUrl = ent.VideoInfo.Variants + .Where(v => v.ContentType == "video/mp4") + .OrderByDescending(v => v.Bitrate) + .Select(v => v.Url).FirstOrDefault(); + media.Add(new MediaInfo(ent.MediaUrl, videoUrl)); + } + else + media.Add(new MediaInfo(ent.MediaUrl)); + } } } } @@ -3034,7 +3046,7 @@ namespace OpenTween } [Obsolete] - public string CreateHtmlAnchor(string text, List AtList, TwitterEntities entities, List media) + public string CreateHtmlAnchor(string text, List AtList, TwitterEntities entities, List media) { return this.CreateHtmlAnchorAsync(text, AtList, entities, media).Result; } -- 2.11.0