diff --git a/src/main/java/org/wikimedia/commons/donvip/spacemedia/data/domain/kari/KariMediaRepository.java b/src/main/java/org/wikimedia/commons/donvip/spacemedia/data/domain/kari/KariMediaRepository.java index a96d7dee0..b845d98dd 100644 --- a/src/main/java/org/wikimedia/commons/donvip/spacemedia/data/domain/kari/KariMediaRepository.java +++ b/src/main/java/org/wikimedia/commons/donvip/spacemedia/data/domain/kari/KariMediaRepository.java @@ -1,103 +1,108 @@ package org.wikimedia.commons.donvip.spacemedia.data.domain.kari; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.time.LocalDate; import java.util.List; +import java.util.Optional; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.Query; import org.wikimedia.commons.donvip.spacemedia.data.domain.base.MediaProjection; import org.wikimedia.commons.donvip.spacemedia.data.domain.base.MediaRepository; public interface KariMediaRepository extends MediaRepository { @Retention(RetentionPolicy.RUNTIME) @CacheEvict(allEntries = true, cacheNames = { "kariCount", "kariCountIgnored", "kariCountMissing", "kariCountMissingImages", "kariCountUploaded", "kariFindByPhashNotNull" }) @interface CacheEvictKariAll { } @Override @CacheEvictKariAll default void evictCaches() { } // COUNT @Override @Cacheable("kariCount") long count(); @Override @Cacheable("kariCountIgnored") long countByIgnoredTrue(); @Override @Cacheable("kariCountMissing") long countMissingInCommons(); @Override @Cacheable("kariCountMissingImages") default long countMissingImagesInCommons() { return countMissingInCommons(); } @Override default long countMissingVideosInCommons() { return 0; } @Override @Cacheable("kariCountUploaded") long countUploadedToCommons(); // FIND + @Query("select max(id) from #{#entityName}") + Optional findMaxId(); + @Override default Page findMissingImagesInCommons(Pageable page) { return findMissingInCommons(page); } @Override default Page findMissingVideosInCommons(Pageable page) { return Page.empty(); } @Override @Cacheable("kariFindByPhashNotNull") List> findByMetadata_PhashNotNull(); // SAVE @Override @CacheEvictKariAll S save(S entity); @Override @CacheEvictKariAll Iterable saveAll(Iterable entities); // DELETE @Override @CacheEvictKariAll void deleteById(Integer id); @Override @CacheEvictKariAll void delete(KariMedia entity); @Override @CacheEvictKariAll void deleteAll(Iterable entities); @Override @CacheEvictKariAll void deleteAll(); } diff --git a/src/main/java/org/wikimedia/commons/donvip/spacemedia/service/agencies/KariService.java b/src/main/java/org/wikimedia/commons/donvip/spacemedia/service/agencies/KariService.java index b69273744..34855db11 100644 --- a/src/main/java/org/wikimedia/commons/donvip/spacemedia/service/agencies/KariService.java +++ b/src/main/java/org/wikimedia/commons/donvip/spacemedia/service/agencies/KariService.java @@ -1,271 +1,274 @@ package org.wikimedia.commons.donvip.spacemedia.service.agencies; import static java.util.Collections.emptyList; import static org.wikimedia.commons.donvip.spacemedia.utils.Utils.newURL; import java.io.IOException; import java.net.URL; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeParseException; import java.time.temporal.Temporal; import java.util.Optional; import java.util.Set; import org.apache.commons.lang3.StringUtils; import org.jsoup.Jsoup; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.TransactionException; import org.wikimedia.commons.donvip.spacemedia.data.domain.base.FileMetadata; import org.wikimedia.commons.donvip.spacemedia.data.domain.kari.KariMedia; import org.wikimedia.commons.donvip.spacemedia.data.domain.kari.KariMediaRepository; import org.wikimedia.commons.donvip.spacemedia.service.MediaService.MediaUpdateResult; import org.wikimedia.commons.donvip.spacemedia.utils.Emojis; @Service public class KariService extends AbstractAgencyService { private static final Logger LOGGER = LoggerFactory.getLogger(KariService.class); + private KariMediaRepository kariRepository; + @Autowired public KariService(KariMediaRepository repository) { super(repository, "kari"); + this.kariRepository = repository; } @Value("${kari.view.link}") private String viewLink; @Value("${kari.kogl.type1}") private String koglType1Icon; @Value("${kari.max.failures:50}") private int maxFailures; @Override protected final Integer getMediaId(String id) { return Integer.parseUnsignedInt(id); } @Override public final String getName() { return "KARI"; } @Override protected String getLanguage(KariMedia media) { return "ko"; } @Override protected Class getMediaClass() { return KariMedia.class; } @Override protected Optional getCreationDate(KariMedia media) { return Optional.ofNullable(media.getCreationDate()); } @Override protected Optional getUploadDate(KariMedia media) { return Optional.ofNullable(media.getDate()); } @Override public final URL getSourceUrl(KariMedia media) { return newURL(getViewUrl(media.getId())); } @Override public final String getSource(KariMedia media) { return "{{KARI-source|" + media.getId() + "|" + media.getKariId() + "}}"; } @Override protected final String getAuthor(KariMedia media) { return "{{Creator:KARI}}"; } private String getViewUrl(int id) { return viewLink.replace("", Integer.toString(id)); } @Override protected boolean checkBlocklist() { return false; } @Override public Set findLicenceTemplates(KariMedia media) { Set result = super.findLicenceTemplates(media); result.add("KOGL"); return result; } @Override public void updateMedia() throws IOException { LocalDateTime start = startUpdateMedia(); int consecutiveFailures = 0; int count = 0; - int id = 1; + int id = kariRepository.findMaxId().orElse(1); while (consecutiveFailures < maxFailures) { boolean save = false; String viewUrl = getViewUrl(id); URL view = newURL(viewUrl); Optional mediaInRepo = repository.findById(id); KariMedia media = mediaInRepo.orElse(null); if (media == null) { KariUpdateResult result = fetchMedia(id); media = result.getMedia(); save = result.getResult(); if (result.isResetConsecutiveFailures()) { consecutiveFailures = 0; } else if (result.isIncrementConsecutiveFailures()) { consecutiveFailures++; } } if (media != null) { try { processMedia(save, media, view, mediaInRepo.isPresent()); count++; } catch (TransactionException e) { LOGGER.error("Transaction error when saving {}", media, e); } } id++; } endUpdateMedia(count, emptyList(), emptyList(), start); } static class KariUpdateResult extends MediaUpdateResult { private final KariMedia media; private final boolean resetConsecutiveFailures; private final boolean incrementConsecutiveFailures; public KariUpdateResult(KariMedia media, boolean result, boolean resetConsecutiveFailures, boolean incrementConsecutiveFailures, Exception exception) { super(result, exception); this.media = media; this.resetConsecutiveFailures = resetConsecutiveFailures; this.incrementConsecutiveFailures = incrementConsecutiveFailures; } public KariMedia getMedia() { return media; } public boolean isResetConsecutiveFailures() { return resetConsecutiveFailures; } public boolean isIncrementConsecutiveFailures() { return incrementConsecutiveFailures; } } private KariUpdateResult fetchMedia(int id) { String viewUrl = getViewUrl(id); URL view = newURL(viewUrl); KariMedia media = null; boolean resetConsecutiveFailures = false; boolean incrementConsecutiveFailures = false; Exception ex = null; try { Element div = Jsoup.connect(viewUrl).timeout(60_000).get().getElementsByClass("board_view").get(0); String title = div.getElementsByTag("h4").get(0).text(); if (!title.isEmpty()) { resetConsecutiveFailures = true; Element infos = div.getElementsByClass("photo_infor").get(0); Elements lis = infos.getElementsByTag("li"); if (lis.get(lis.size() - 1).getElementsByTag("img").attr("src").endsWith(koglType1Icon)) { media = buildMedia(id, view, div, title, infos, lis); } else { LOGGER.info("Media {} exists but appears to be non-free (Not KOGL type 1)", id); } } else { incrementConsecutiveFailures = true; } } catch (DateTimeParseException e) { LOGGER.error("Cannot parse HTML", e); ex = e; } catch (IOException e) { problem(view, e); incrementConsecutiveFailures = true; ex = e; } return new KariUpdateResult(media, media != null, resetConsecutiveFailures, incrementConsecutiveFailures, ex); } protected KariMedia processMedia(boolean save, KariMedia media, URL view, boolean mediaInRepo) throws IOException { if (StringUtils.isBlank(media.getDescription())) { problem(view, "Empty description"); } FileMetadata metadata = media.getMetadata().get(0); if (metadata.getAssetUrl() != null) { String mediaUrl = metadata.getAssetUrl().toExternalForm(); if (StringUtils.isBlank(mediaUrl) || "https://www.kari.re.kr".equals(mediaUrl)) { metadata.setAssetUrl(null); } } if (metadata.getAssetUrl() == null) { problem(view, "No download link"); save = false; if (mediaInRepo) { deleteMedia(media, "No download link"); } } if (doCommonUpdate(media)) { save = true; } return saveMediaOrCheckRemote(save, media); } private KariMedia buildMedia(int id, URL view, Element div, String title, Element infos, Elements lis) { KariMedia media = new KariMedia(); media.setId(id); media.setKariId(lis.get(0).getElementsByClass("txt").get(0).text()); media.setTitle(title); media.setDate(LocalDate.parse(div.getElementsByClass("infor").get(0).getElementsByTag("li") .get(0).getElementsByClass("txt").get(0).text())); media.setDescription(div.getElementsByClass("photo_txt").get(0).text()); String href = infos.getElementsByTag("a").attr("href"); if (StringUtils.isNotBlank(href)) { addMetadata(media, newURL(view.getProtocol(), view.getHost(), href), null); } String src = div.getElementsByClass("board_txt").get(0).getElementsByTag("img").attr("src").replace("/view/", "/lst/"); if (StringUtils.isNotBlank(src)) { media.setThumbnailUrl(newURL(view.getProtocol(), view.getHost(), src)); } return media; } @Override protected boolean isPermittedFileType(FileMetadata metadata) { return metadata.getAssetUrl() != null && metadata.getAssetUrl().toExternalForm() .startsWith("https://www.kari.re.kr/image/kari_image_down.do?"); } @Override protected KariMedia refresh(KariMedia media) throws IOException { KariMedia mediaFromApi = fetchMedia(media.getId()).getMedia(); return mediaFromApi != null ? media.copyDataFrom(mediaFromApi) : null; } @Override protected Set getEmojis(KariMedia uploadedMedia) { return Set.of(Emojis.FLAG_KOR); } @Override protected Set getTwitterAccounts(KariMedia uploadedMedia) { return Set.of("@kari2030"); } } diff --git a/src/main/resources/application-job-kari.properties b/src/main/resources/application-job-kari.properties index 3f223bca0..850fe422c 100644 --- a/src/main/resources/application-job-kari.properties +++ b/src/main/resources/application-job-kari.properties @@ -1,5 +1,6 @@ spring.jpa.properties.hibernate.search.enabled = false commons.datasource.hikari.maximum-pool-size = 2 domain.datasource.hikari.maximum-pool-size = 2 agency = org.wikimedia.commons.donvip.spacemedia.service.agencies.KariService repositoryClass = org.wikimedia.commons.donvip.spacemedia.data.domain.kari.KariMediaRepository +kari.upload = auto diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 875fa1f25..63c3ff843 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,266 +1,266 @@ application.version = ${project.version} application.contact = https://commons.wikimedia.org/wiki/Commons:Spacemedia flickr4java.version = ${flickr4java-version} springboot.version = ${springboot-version} scribejava.version = ${scribejava-version} update.fullres.images = true perceptual.threshold = 0.2 perceptual.threshold.identicalid = 0.0785 reset.ignored = false reset.perceptual.hashes = false reset.sha1.hashes = false reset.problems = false server.error.whitelabel.enabled=false server.error.include-exception=true server.error.include-message=ALWAYS search.enabled = true threads.number = 8 audios.enabled = false videos.enabled = false upload.auto.min.year = 2023 management.endpoints.web.exposure.include=health,info,metrics,logfile remote.application.uri = https://spacemedia.toolforge.org # Duplicates / Hashes commons.duplicates.max.files = 101 commons.duplicates.social.media.threshold = 100 # Agencies arianespace.youtube.channels = UCRn9F2D9j-t4A-HgudM7aLQ djangoplicity.date.pattern = d MMMM yyyy djangoplicity.datetime.pattern = d MMMM yyyy, HH:mm dlr.flickr.accounts = dlr_de dlr.upload = manual esa.search.link = https://www.esa.int/ESA_Multimedia/Search/(offset)//(sortBy)/published?SearchText=by-sa esa.max.tries = 5 esa.date.pattern = dd/MM/yyyy esa.upload = auto esa.flickr.accounts = europeanspaceagency,esa_events eso.search.link = https://www.eso.org/public/images/viewall/list//?sort=-release_date eso.upload = auto_from_date hubble.esa.search.link = https://esahubble.org/images/viewall/page//?sort=-release_date hubble.esa.upload = auto_from_date webb.esa.search.link = https://esawebb.org/images/page//?sort=-release_date webb.esa.upload = auto hubble.nasa.search.link = https://hubblesite.org/resource-gallery/images?itemsPerPage=100&page= hubble.nasa.detail.link = https://hubblesite.org/contents/media/images/ hubble.nasa.upload = auto_from_date webb.nasa.search.link = https://webbtelescope.org/resource-gallery/images?itemsPerPage=100&page= webb.nasa.detail.link = https://webbtelescope.org/contents/media/images/ webb.nasa.upload = auto iau.search.link = https://www.iau.org/public/images/list//?sort=-release_date iau.upload = auto_from_date individuals.flickr.accounts = harrystrangerphotography,194849271@N04,192271236@N03,geckzilla,pierre_markuse,kevinmgill individuals.upload = auto_from_date kari.view.link = https://www.kari.re.kr/kor/kariimg/view.do?idx= kari.kogl.type1 = img_opentype01.jpg -kari.upload = manual +kari.upload = auto nasa.search.link = https://images-api.nasa.gov/search? nasa.details.link = https://images.nasa.gov/details/ nasa.metadata.link = https://images-assets.nasa.gov/image//metadata.json nasa.min.year = 1920 nasa.max.tries = 5 nasa.centers = AFRC,ARC,GRC,GSFC,GISS,HQ,IVV,JPL,JSC,KSC,LARC,LRC,MAF,MSFC,NESC,NSC,NSSC,PBS,SSC,WSFC,WSTF nasa.upload = auto_from_date nasa.afrc.home.page = https://www.nasa.gov/centers/armstrong/ nasa.arc.home.page = http://www.nasa.gov/centers/ames/ nasa.grc.home.page = http://www.nasa.gov/centers/glenn/ nasa.gsfc.home.page = http://www.nasa.gov/centers/goddard/ nasa.giss.home.page = http://www.giss.nasa.gov/ nasa.hq.home.page = http://www.nasa.gov/centers/hq/ nasa.ivv.home.page = http://www.nasa.gov/centers/ivv/ nasa.jpl.home.page = http://www.nasa.gov/centers/jpl/ nasa.jsc.home.page = http://www.nasa.gov/centers/johnson/ nasa.ksc.home.page = http://www.nasa.gov/centers/kennedy/ nasa.larc.home.page = http://www.nasa.gov/centers/langley/ nasa.lrc.home.page = http://www.nasa.gov/centers/langley/ nasa.maf.home.page = http://www.nasa.gov/centers/marshall/michoud/ nasa.msfc.home.page = http://www.nasa.gov/centers/marshall/ nasa.nesc.home.page = http://www.nasa.gov/offices/nesc/ nasa.nsc.home.page = http://www.nasa.gov/offices/nsc/ nasa.nssc.home.page = http://www.nssc.nasa.gov/ nasa.pbl.home.page = http://www.nasa.gov/centers/glenn/about/testfacilities/ nasa.ssc.home.page = https://www.nasa.gov/centers/stennis/ nasa.wsfc.home.page = http://www.nasa.gov/centers/wallops/ nasa.wstf.home.page = http://www.nasa.gov/centers/wstf/ nasa.afrc.name = NASA Armstrong Flight Research Center nasa.arc.name = NASA Ames Research Center nasa.grc.name = NASA Glenn Research Center nasa.gsfc.name = NASA Goddard Space Flight Center nasa.giss.name = NASA Goddard Institute for Space Studies nasa.hq.name = NASA Headquarters nasa.ivv.name = NASA Independent Verification and Validation Facility nasa.jpl.name = NASA Jet Propulsion Laboratory nasa.jsc.name = NASA Johnson Space Center nasa.ksc.name = NASA Kennedy Space Center nasa.larc.name = NASA Langley Research Center nasa.lrc.name = NASA Langley Research Center nasa.maf.name = NASA Michoud Assembly Facility nasa.msfc.name = NASA Marshall Space Flight Center nasa.nesc.name = NASA Engineering and Safety Center nasa.nsc.name = NASA Safety Center nasa.nssc.name = NASA Shared Services Center nasa.pbs.name = NASA Plum Brook Station nasa.ssc.name = NASA Stennis Space Center nasa.wsfc.name = NASA Wallops Flight Facility nasa.wstf.name = NASA White Sands Test Facility nasa.box.app-shares = nasa-external-ocomm:onrtmdvofqluv5ei5kfu5u1pf8v4xqtl nasa.box.upload = manual nasa.flickr.accounts = atmospheric-infrared-sounder,ghrcdaac,nasawebbtelescope,40054892@N06,nasa_appel,nasadfrc,nasaearthobservatory,earthrightnow,nasaedge,nasafo,nasaglenn,gsfc,nasa_goddard,nasahqphoto,nasahubble,nasa_ice,nasa2explore,nasa_jsc_photo,nasa_larss,nasacommons,nasampcv,nasaorion,nasaspaceflightawareness,nasa_langley,nasamarshall,eospso,nasaarmstrong,nasablueshift,nasacolab,nasa-jpl,nasakennedy,nasamarshallphotos,nasarobonaut,oursolarsystem,nasa_hsf,sdomission,larc-science,uahirise-mars,43066628@N07,nasadesertrats,hmpresearchstation,morpheuslander nasa.flickr.upload = manual nasa.aster.gallery.url = https://asterweb.jpl.nasa.gov/map/gmtext.asp nasa.aster.details.url = https://asterweb.jpl.nasa.gov/gallery-detail.asp?name= nasa.aster.upload = auto_from_date nasa.sdo.upload = manual nasa.sirs.categories.url = https://www.ssc.nasa.gov/sirs/scripts/browse-html.pl nasa.sirs.images.url = https://www.ssc.nasa.gov/sirs/scripts/browse-xml.pl?mediatype=photos&category=&Page= nasa.sirs.details.url = https://www.ssc.nasa.gov/sirs/scripts/getxmlfile.pl?id= nasa.photojournal.solr.host = https://photojournal.jpl.nasa.gov/beta/solr/photojournal nasa.photojournal.solr.retries = 200 nasa.photojournal.geohack.globes = Ariel,Callisto,Ceres,Charon,Deimos,Dione,Enceladus,Europa,Ganymede,Hyperion,Iapetus,Io,Jupiter,Mars,Mercury,Mimas,Miranda,Moon,Oberon,Phobos,Phoebe,Pluto,Rhea,Saturn,Tethys,Titan,Triton,Umbriel,Venus,Vesta nasa.photojournal.upload = manual noirlab.search.link = https://noirlab.edu/public/images/viewall/page//?sort=-release_date noirlab.date.pattern = MMMM d, yyyy noirlab.datetime.pattern1 = MMM d, yyyy, h:m a noirlab.datetime.pattern2 = MMMM d, yyyy, h:m a noirlab.datetime.pattern3 = MMMM d, yyyy, h a noirlab.datetime.pattern4 = MMM d, yyyy, B noirlab.datetime.pattern5 = MMMM d, yyyy, B noirlab.datetime.pattern6 = MMM d, yyyy, h a noirlab.upload = auto_from_date potential.flickr.accounts = arianespace,cnes,27110053@N05,roscosmos,spacegovuk,lockheedmartin,airbusgroup,northropgrumman,orbital-atk,nanoracks,virgingalactic,ulalaunch,spacegeneration,125959264@N05 spacex.flickr.accounts = spacex usspaceforce.flickr.accounts = airforcespacecommand,129133022@N07 usspaceforce.flickr.upload = auto usspaceforce.dvids.units = 21SW,SLD30,45SW,50SW,310SW,460SW-PA,USSF-PA,SBD1,B-GAR,SpOC,AFSC,SSC,USSPACECOM,STARCOM usspaceforce.dvids.min.year = 1986 usspaceforce.dvids.upload = auto # Spring spring.main.allow-circular-references=true spring.jackson.default-property-inclusion=non_empty spring.jackson.property-naming-strategy=SNAKE_CASE spring.jpa.hibernate.ddl-auto=update spring.jpa.open-in-view=false spring.jpa.show-sql=false spring.jpa.properties.hibernate.format_sql=false spring.jpa.properties.hibernate.search.backend.type = lucene spring.jpa.properties.hibernate.search.backend.directory.root=cache/index/default spring.jpa.properties.hibernate.auto_quote_keyword=true # Local database for development domain.datasource.url=jdbc:mariadb://localhost:3306/root_spacemedia domain.datasource.username=root domain.datasource.password= domain.datasource.driver-class-name=org.mariadb.jdbc.Driver domain.datasource.hikari.maximum-pool-size=4 domain.datasource.hikari.max-lifetime=300000 domain.datasource.hikari.connectionInitSql = SET NAMES 'utf8mb4' commons.datasource.url=jdbc:mariadb://localhost:3307/commonswiki_p commons.datasource.username= commons.datasource.password= commons.datasource.driver-class-name=org.mariadb.jdbc.Driver commons.datasource.hikari.maximum-pool-size=4 commons.datasource.hikari.max-lifetime=300000 commons.datasource.hikari.connectionInitSql = SET NAMES 'utf8mb4' commons.api.url = https://commons.wikimedia.org/w/api.php commons.api.rest.url = https://commons.wikimedia.org/w/rest.php commons.api.account = OptimusPrimeBot commons.api.oauth1.consumer-token = commons.api.oauth1.consumer-secret = commons.api.oauth1.access-token = commons.api.oauth1.access-secret = commons.cat.search.depth = 1 commons.img.preview.width = 640 commons.duplicate.url = https://commons.wikimedia.org/w/index.php?title=Special:ListDuplicatedFiles commons.ignored.duplicates.sha1 = 2a1k43x3t0g6uj4x5h9276m58uoh7ry,mlgsj749r17q6zs5es2u8n3u2j6wod7 commons.ignored.duplicates.name = Test.pdf commons.ignored.duplicates.cats = Election apportionment diagrams # SHA1 hashes to skip. Seems related to corrupted files ignored.sha1 = da39a3ee5e6b4b0d3255bfef95601890afd80709 # Perceptual hashes to skip. Common among dozens/hundreds of files (seems related to transparency / alpha channel) ignored.phash = 6dp5qcb22im238nr3wvp0ic7q99w035jmy2iw7i6n43d37jtof commons.dpla.max.duplicates = 5 commons.permitted.file.types = tiff,tif,png,gif,jpg,jpeg,webp,xcf,pdf,mid,ogg,ogv,svg,djvu,stl,oga,flac,opus,wav,webm,mp3,midi,mpg,mpeg dvids.api.key = dvids.media.url = https://www.dvidshub.net/{type}/{id} dvids.api.search.url = https://api.dvidshub.net/search?api_key={api_key}&type={type}&unit={unit}&fields=id&page={page} dvids.api.search.year.url = https://api.dvidshub.net/search?api_key={api_key}&type={type}&unit={unit}&from_date={from_date}&to_date={to_date}&fields=id&page={page} dvids.api.asset.url = https://api.dvidshub.net/asset?api_key={api_key}&id={id}&fields=id,aspect_ratio,branch,credit,description,duration,category,keywords,date,date_published,files,image,location,dimensions,rating,thumbnail,timestamp,time_start,title,unit_name,url,virin dvids.ignored.categories = Greetings,Interviews,Newscasts,Briefings,PSA,Commercials flickr.video.download.url = https://www.flickr.com/video_download.gne?id= dozer.mapping-files=classpath:/mapping.xml youtube.client.id = youtube.client.secret = youtube.auth.uri = https://accounts.google.com/o/oauth2/auth youtube.token.uri = https://oauth2.googleapis.com/token google.translate.project = google.translate.location = global google.translate.privateKeyId = google.translate.privateKey = google.translate.clientEmail = google.translate.clientId = box.api.oauth2.client-id = box.api.oauth2.client-secret = box.api.user-email = box.api.user-password = mastodon.instance = mastodon.online mastodon.api.oauth2.client-id = mastodon.api.oauth2.client-secret = mastodon.api.oauth2.access-token = twitter.api.oauth1.consumer-token = twitter.api.oauth1.consumer-secret = twitter.api.oauth1.access-token = twitter.api.oauth1.access-secret = courtesy.ok=courtesy of nasa,courtesy of u.s.,courtesy of united states,air force courtesy photo,air force courtesy graphic,space force courtesy photo,space force courtesy graphic,nformation courtesy of