Update fanbox download

Now will read exif data before write exif data.
This commit is contained in:
2022-07-15 05:09:42 +00:00
committed by GitHub
parent 061aa56bde
commit 70b87e055d
12 changed files with 343 additions and 35 deletions

View File

@@ -29,6 +29,7 @@ typedef struct ExifDataRef ExifDataRef;
EXIF_API ExifImage* create_exif_image(const char* path);
EXIF_API ExifDataRef* exif_image_get_exif_data(ExifImage* image);
EXIF_API int exif_image_read_metadata(ExifImage* image);
EXIF_API int exif_image_set_exif_data(ExifImage* image, ExifData* data);
EXIF_API int exif_image_write_metadata(ExifImage* image);
EXIF_API void free_exif_image(ExifImage* img);

View File

@@ -30,6 +30,17 @@ ExifDataRef* exif_image_get_exif_data(ExifImage* image) {
return &image->exif_data_ref;
}
int exif_image_read_metadata(ExifImage* image) {
if (!image) return 1;
try {
image->image->readMetadata();
} catch (std::exception& e) {
printf("%s\n", e.what());
return 1;
}
return 0;
}
int exif_image_set_exif_data(ExifImage* image, ExifData* data) {
if (!image || !data) return 1;
image->image->setExifData(data->data);

View File

@@ -701,3 +701,37 @@ pub fn derive_check_unknown(item: TokenStream) -> TokenStream {
);
stream.into()
}
struct CreateFanboxDownloadHelper {
pub fn_name: Ident,
}
impl Parse for CreateFanboxDownloadHelper {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let fn_name = input.parse()?;
Ok(Self { fn_name })
}
}
#[proc_macro]
pub fn create_fanbox_download_helper(item: TokenStream) -> TokenStream {
let CreateFanboxDownloadHelper { fn_name } =
parse_macro_input!(item as CreateFanboxDownloadHelper);
let defn_name = Ident::new(
format!("download_{}", fn_name.to_string()).as_str(),
fn_name.span(),
);
let stream = quote!(
#[inline]
pub fn #defn_name(&self) -> Result<Option<crate::downloader::DownloaderHelper>, crate::downloader::DownloaderError> {
match self.#fn_name() {
Some(url) => {
let client: &std::sync::Arc<crate::webclient::WebClient> = self.client.as_ref().as_ref();
Ok(Some(crate::downloader::DownloaderHelper::builder(url)?.client(client).build()))
}
None => Ok(None),
}
}
);
stream.into()
}

View File

@@ -1,3 +1,5 @@
#[cfg(feature = "exif")]
use super::exif::ExifDataSource;
use crate::gettext;
use crate::opt::author_name_filter::AuthorFiler;
use crate::opthelper::get_helper;
@@ -84,3 +86,22 @@ impl PixivData {
}
}
}
#[cfg(feature = "exif")]
impl ExifDataSource for PixivData {
fn image_author(&self) -> Option<String> {
self.author.clone()
}
fn image_comment(&self) -> Option<String> {
self.description.clone()
}
fn image_id(&self) -> Option<String> {
Some(self.id.to_link())
}
fn image_title(&self) -> Option<String> {
self.title.clone()
}
}

View File

@@ -1,18 +1,39 @@
use crate::data::data::PixivData;
use crate::exif::ExifByteOrder;
use crate::exif::ExifData;
use crate::exif::ExifImage;
use crate::exif::ExifKey;
use crate::exif::ExifTypeID;
use crate::exif::ExifValue;
use crate::ext::try_err::TryErr;
use crate::parser::description::parse_description;
use std::convert::TryFrom;
use std::ffi::OsStr;
use utf16string::LittleEndian;
use utf16string::WString;
fn add_image_id(data: &mut ExifData, d: &PixivData) -> Result<(), ()> {
let link = d.id.to_link();
pub trait ExifDataSource {
fn image_author(&self) -> Option<String> {
None
}
fn image_comment(&self) -> Option<String> {
None
}
fn image_id(&self) -> Option<String> {
None
}
fn image_title(&self) -> Option<String> {
None
}
}
fn add_image_id<D: ExifDataSource>(data: &mut ExifData, d: &D) -> Result<(), ()> {
let link = match d.image_id() {
Some(link) => link,
None => return Ok(()),
};
let key = ExifKey::try_from("Exif.Image.ImageID")?;
let mut value = ExifValue::try_from(ExifTypeID::AsciiString)?;
value.read(link.as_bytes(), None)?;
@@ -20,31 +41,33 @@ fn add_image_id(data: &mut ExifData, d: &PixivData) -> Result<(), ()> {
Ok(())
}
fn add_image_title(data: &mut ExifData, d: &PixivData) -> Result<(), ()> {
if d.title.is_none() {
return Ok(());
}
let title = d.title.as_ref().unwrap();
fn add_image_title<D: ExifDataSource>(data: &mut ExifData, d: &D) -> Result<(), ()> {
let title = match d.image_title() {
Some(title) => title,
None => return Ok(()),
};
let key = ExifKey::try_from("Exif.Image.ImageDescription")?;
let mut value = ExifValue::try_from(ExifTypeID::AsciiString)?;
value.read(title.as_bytes(), None)?;
data.add(&key, &value)?;
let key = ExifKey::try_from("Exif.Image.XPTitle")?;
let mut value = ExifValue::try_from(ExifTypeID::BYTE)?;
let s: WString<LittleEndian> = WString::from(title);
let s: WString<LittleEndian> = WString::from(title.as_str());
value.read(s.as_bytes(), None)?;
data.add(&key, &value)?;
Ok(())
}
fn add_image_author(data: &mut ExifData, d: &PixivData) -> Result<(), ()> {
if d.author.is_none() {
return Ok(());
}
let author = d.author.as_ref().unwrap();
fn add_image_author<D: ExifDataSource>(data: &mut ExifData, d: &D) -> Result<(), ()> {
let author = match d.image_author() {
Some(author) => author,
None => {
return Ok(());
}
};
let key = ExifKey::try_from("Exif.Image.XPAuthor")?;
let mut value = ExifValue::try_from(ExifTypeID::BYTE)?;
let s: WString<LittleEndian> = WString::from(author);
let s: WString<LittleEndian> = WString::from(author.as_str());
value.read(s.as_bytes(), None)?;
data.add(&key, &value)?;
let key = ExifKey::try_from("Exif.Image.Artist")?;
@@ -54,19 +77,20 @@ fn add_image_author(data: &mut ExifData, d: &PixivData) -> Result<(), ()> {
Ok(())
}
fn add_image_comment(data: &mut ExifData, d: &PixivData) -> Result<(), ()> {
if d.description.is_none() {
return Ok(());
}
let desc = parse_description(d.description.as_ref().unwrap());
let desc = if desc.is_some() {
desc.as_ref().unwrap()
} else {
d.description.as_ref().unwrap()
fn add_image_comment<D: ExifDataSource>(data: &mut ExifData, d: &D) -> Result<(), ()> {
let odesc = match d.image_comment() {
Some(desc) => desc,
None => {
return Ok(());
}
};
let desc = match parse_description(odesc.as_str()) {
Some(desc) => desc,
None => odesc,
};
let key = ExifKey::try_from("Exif.Image.XPComment")?;
let mut value = ExifValue::try_from(ExifTypeID::BYTE)?;
let s: WString<LittleEndian> = WString::from(desc);
let s: WString<LittleEndian> = WString::from(desc.as_str());
value.read(s.as_bytes(), None)?;
data.add(&key, &value)?;
Ok(())
@@ -80,13 +104,14 @@ fn add_image_page(data: &mut ExifData, page: u16) -> Result<(), ()> {
Ok(())
}
pub fn add_exifdata_to_image<S: AsRef<OsStr> + ?Sized>(
pub fn add_exifdata_to_image<S: AsRef<OsStr> + ?Sized, D: ExifDataSource>(
file_name: &S,
data: &PixivData,
data: &D,
page: u16,
) -> Result<(), ()> {
let mut f = ExifImage::new(file_name)?;
let mut d = ExifData::new()?;
f.read_metadata()?;
let mut d = f.exif_data().try_err(())?.to_owned();
add_image_id(&mut d, data)?;
add_image_title(&mut d, data)?;
add_image_author(&mut d, data)?;

View File

@@ -28,6 +28,7 @@ use crate::Main;
use indicatif::MultiProgress;
use json::JsonValue;
use reqwest::IntoUrl;
use std::fs::create_dir;
use std::path::PathBuf;
use std::sync::Arc;
@@ -130,7 +131,7 @@ impl Main {
if d.is_downloaded() {
#[cfg(feature = "exif")]
{
if add_exifdata_to_image(&file_name, &datas, np).is_err() {
if add_exifdata_to_image(&file_name, &*datas, np).is_err() {
println!(
"{} {}",
gettext("Failed to add exif data to image:"),
@@ -149,7 +150,7 @@ impl Main {
#[cfg(feature = "exif")]
{
if helper.update_exif() && file_name.exists() {
if add_exifdata_to_image(&file_name, &datas, np).is_err() {
if add_exifdata_to_image(&file_name, &*datas, np).is_err() {
println!(
"{} {}",
gettext("Failed to add exif data to image:"),
@@ -402,16 +403,36 @@ impl Main {
// #TODO allow to continue
return Ok(());
}
let base = Arc::new(PathBuf::from(format!("./{}", id.post_id)));
let json_file = base.join("body.json");
let base = Arc::new(PathBuf::from(format!("./{}/{}", id.creator_id, id.post_id)));
let json_file = base.join("data.json");
let data = FanboxData::new(id, &post).try_err("Failed to create data file.")?;
let data_file = JSONDataFile::from(data);
if !base.exists() {
match create_dir(&*base) {
Ok(_) => {}
Err(e) => {
if !base.exists() {
return Err(PixivDownloaderError::from(e));
}
}
}
}
data_file
.save(&json_file)
.try_err(gettext("Failed to save post data to file."))?;
match post {
FanboxPost::Article(article) => {}
FanboxPost::Image(img) => {}
FanboxPost::Image(img) => {
let body = img
.body()
.try_err(gettext("Failed to get the body of image post."))?;
let text = body
.text()
.try_err(gettext("Failed to get the text of the image post."))?;
let images = body
.images()
.try_err(gettext("Failed to get images from the image post."))?;
}
FanboxPost::Unknown(_) => {
return Err(PixivDownloaderError::from(gettext(
"Unrecognized post type.",

165
src/downloader/helper.rs Normal file
View File

@@ -0,0 +1,165 @@
use super::downloader::Downloader;
use super::enums::DownloaderResult;
use super::error::DownloaderError;
use super::local_file::LocalFile;
use crate::ext::replace::ReplaceWith;
use crate::gettext;
use crate::webclient::ToHeaders;
use crate::webclient::WebClient;
use reqwest::IntoUrl;
use std::convert::TryFrom;
use std::path::Path;
use std::sync::Arc;
use url::Url;
/// Store download information
pub struct DownloaderHelper {
/// Url
pub url: Url,
/// Web client
pub client: Option<Arc<WebClient>>,
/// New headers wants to apply
pub headers: Option<Box<dyn ToHeaders>>,
/// Recommand file name
pub file_name: Option<Box<dyn AsRef<Path>>>,
}
pub struct DownloaderHelperBuilder {
helper: DownloaderHelper,
}
impl DownloaderHelper {
pub fn builder<U: IntoUrl>(url: U) -> Result<DownloaderHelperBuilder, DownloaderError> {
Ok(DownloaderHelperBuilder {
helper: Self {
url: url.into_url()?,
client: None,
headers: None,
file_name: None,
},
})
}
/// Get A local [Downloader]
/// * `overwrite` - Whether to overwrite file
/// * `base` - The base directory to store downloaded files.
pub fn download_local<P: AsRef<Path> + ?Sized>(
&self,
overwrite: Option<bool>,
base: &P,
) -> Result<DownloaderResult<Downloader<LocalFile>>, DownloaderError> {
let base = base.as_ref();
let file = match &self.file_name {
Some(file_name) => base.join(file_name.as_ref()),
None => match crate::utils::get_file_name_from_url(self.url.clone()) {
Some(file_name) => base.join(file_name),
None => {
return Err(DownloaderError::from(gettext(
"Failed to get file name from url.",
)));
}
},
};
let headers = match &self.headers {
Some(headers) => headers.to_headers(),
None => None,
};
match &self.client {
Some(client) => Downloader::<LocalFile>::new2(
Arc::clone(client),
self.url.clone(),
headers,
Some(&file),
overwrite,
),
None => Downloader::<LocalFile>::new(self.url.clone(), headers, Some(&file), overwrite),
}
}
pub fn set_client(&mut self, client: &Arc<WebClient>) {
self.client.replace(Arc::clone(client));
}
pub fn set_file_name<P: AsRef<Path> + ?Sized>(&mut self, p: &P) {
self.file_name.replace(Box::new(p.as_ref().to_owned()));
}
pub fn set_headers<H: ToHeaders>(&mut self, headers: H) {
self.headers.replace_with(match headers.to_headers() {
Some(headers) => Some(Box::new(headers)),
None => None,
});
}
pub fn set_no_client(&mut self) {
self.client.take();
}
pub fn set_no_file_name(&mut self) {
self.file_name.take();
}
pub fn set_no_headers(&mut self) {
self.headers.take();
}
}
impl DownloaderHelperBuilder {
pub fn build(self) -> DownloaderHelper {
self.helper
}
pub fn client(mut self, client: &Arc<WebClient>) -> Self {
self.helper.set_client(client);
self
}
pub fn file_name<P: AsRef<Path> + ?Sized>(mut self, p: &P) -> Self {
self.helper.set_file_name(p);
self
}
pub fn headers<H: ToHeaders>(mut self, headers: H) -> Self {
self.helper.set_headers(headers);
self
}
pub fn no_client(mut self) -> Self {
self.helper.set_no_client();
self
}
pub fn no_file_name(mut self) -> Self {
self.helper.set_no_file_name();
self
}
pub fn no_headers(mut self) -> Self {
self.helper.set_no_headers();
self
}
}
impl From<Url> for DownloaderHelper {
fn from(url: Url) -> Self {
Self {
url,
client: None,
headers: None,
file_name: None,
}
}
}
impl From<&Url> for DownloaderHelper {
fn from(u: &Url) -> Self {
Self::from(u.clone())
}
}
impl<'a> TryFrom<&'a str> for DownloaderHelper {
type Error = url::ParseError;
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
Ok(Self::from(Url::parse(value)?))
}
}

View File

@@ -4,6 +4,8 @@ pub mod downloader;
pub mod enums;
/// File downloader's error
pub mod error;
/// Downloader helper
pub mod helper;
/// Local file type
pub mod local_file;
/// The pd file
@@ -13,4 +15,5 @@ pub mod tasks;
pub use downloader::Downloader;
pub use enums::DownloaderResult;
pub use error::DownloaderError;
pub use helper::DownloaderHelper;
pub use local_file::LocalFile;

View File

@@ -13,6 +13,7 @@ pub enum PixivDownloaderError {
#[cfg(feature = "server")]
Hyper(hyper::Error),
HTTP(http::Error),
IOError(std::io::Error),
}
impl From<&str> for PixivDownloaderError {

View File

@@ -701,6 +701,21 @@ impl ExifImage {
unsafe { Some(ExifDataRef::from_raw_handle(d)) }
}
/// Read all metadata supported by a specific image format from the image. Before this method is called, the image metadata will be cleared.
///
/// This method returns success even if no metadata is found in the image. Callers must therefore check the size of individual metadata types before accessing the data.
pub fn read_metadata(&mut self) -> Result<(), ()> {
if self.img.is_null() {
return Err(());
}
let d = unsafe { _exif::exif_image_read_metadata(self.img) };
if d == 0 {
Ok(())
} else {
Err(())
}
}
/// Assign new Exif data.
/// The new Exif data is not written to the image until the [Self::write_metadata()] method is called.
/// * `data` - An [ExifData] instance holding Exif data to be copied

View File

@@ -3,6 +3,7 @@ use super::super::error::FanboxAPIError;
use crate::fanbox_api::FanboxClientInternal;
use json::JsonValue;
use proc_macros::check_json_keys;
use proc_macros::create_fanbox_download_helper;
use std::fmt::Debug;
use std::sync::Arc;
@@ -41,11 +42,15 @@ impl FanboxArticleImage {
self.data["originalUrl"].as_str()
}
create_fanbox_download_helper!(original_url);
#[inline]
pub fn thumbnail_url(&self) -> Option<&str> {
self.data["thumbnailUrl"].as_str()
}
create_fanbox_download_helper!(thumbnail_url);
#[inline]
pub fn width(&self) -> Option<u64> {
self.data["width"].as_u64()

View File

@@ -21,7 +21,7 @@ use std::sync::RwLock;
/// Fanbox API client
pub struct FanboxClientInternal {
/// Web client
client: WebClient,
client: Arc<WebClient>,
/// true if in is initialized
inited: AtomicBool,
/// Fanbox global data
@@ -65,7 +65,7 @@ impl FanboxClientInternal {
/// Create an new instance
pub fn new() -> Self {
Self {
client: WebClient::default(),
client: Arc::new(WebClient::default()),
inited: AtomicBool::new(false),
data: RwLock::new(None),
}
@@ -273,6 +273,12 @@ impl FanboxClientInternal {
}
}
impl AsRef<Arc<WebClient>> for FanboxClientInternal {
fn as_ref(&self) -> &Arc<WebClient> {
&self.client
}
}
/// Fanbox API Client
pub struct FanboxClient {
/// Internal client