mirror of
https://github.com/lifegpc/pixiv_downloader.git
synced 2026-07-08 01:32:41 +08:00
Update fanbox download
Now will read exif data before write exif data.
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
@@ -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
165
src/downloader/helper.rs
Normal 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)?))
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
15
src/exif.rs
15
src/exif.rs
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user