mirror of
https://github.com/bellingcat/auto-archiver.git
synced 2026-06-12 21:28:29 +03:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf3c04b3fc | ||
|
|
7eebecdb2c | ||
|
|
b17b5953dd | ||
|
|
ceb717ea65 | ||
|
|
6e4fb76940 | ||
|
|
810a31b1f0 | ||
|
|
8b15d733b1 | ||
|
|
ca37d54b7f | ||
|
|
a1742b5565 | ||
|
|
60a1f3a27a |
6
Pipfile.lock
generated
6
Pipfile.lock
generated
@@ -1643,11 +1643,11 @@
|
|||||||
},
|
},
|
||||||
"vk-url-scraper": {
|
"vk-url-scraper": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:dc1c5cebc2919620935be0b669bef899dca3b8a3ef6419a81c97d03903b012a5",
|
"sha256:b81caa8738b0154aca25ab33012e8e4d8dc11cdc9fd371968afbbadd57e2e1ab",
|
||||||
"sha256:edf155fde3e86950f715a4de09cbe975cf220cb1fcc2fa239dd27868d991ffb3"
|
"sha256:d12aec2828e8e47c766991207fd809e402ab832ea449129b94ac7eadb0d1a27c"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==0.3.24"
|
"version": "==0.3.26"
|
||||||
},
|
},
|
||||||
"warcio": {
|
"warcio": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ class Media:
|
|||||||
try:
|
try:
|
||||||
streams = ffmpeg.probe(self.filename, select_streams='v')['streams']
|
streams = ffmpeg.probe(self.filename, select_streams='v')['streams']
|
||||||
logger.warning(f"STREAMS FOR {self.filename} {streams}")
|
logger.warning(f"STREAMS FOR {self.filename} {streams}")
|
||||||
return any(s.get("duration_ts") > 0 for s in streams)
|
return any(s.get("duration_ts", 0) > 0 for s in streams)
|
||||||
except Error: return False # ffmpeg errors when reading bad files
|
except Error: return False # ffmpeg errors when reading bad files
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
|
|||||||
@@ -26,11 +26,16 @@ class PdqHashEnricher(Enricher):
|
|||||||
def enrich(self, to_enrich: Metadata) -> None:
|
def enrich(self, to_enrich: Metadata) -> None:
|
||||||
url = to_enrich.get_url()
|
url = to_enrich.get_url()
|
||||||
logger.debug(f"calculating perceptual hashes for {url=}")
|
logger.debug(f"calculating perceptual hashes for {url=}")
|
||||||
|
media_with_hashes = []
|
||||||
|
|
||||||
for m in to_enrich.media:
|
for m in to_enrich.media:
|
||||||
for media in m.all_inner_media(True):
|
for media in m.all_inner_media(True):
|
||||||
if media.is_image() and "screenshot" not in media.get("id") and "warc-file-" not in media.get("id") and len(hd := self.calculate_pdq_hash(media.filename)):
|
media_id = media.get("id", "")
|
||||||
|
if media.is_image() and "screenshot" not in media_id and "warc-file-" not in media_id and len(hd := self.calculate_pdq_hash(media.filename)):
|
||||||
media.set("pdq_hash", hd)
|
media.set("pdq_hash", hd)
|
||||||
|
media_with_hashes.append(media.filename)
|
||||||
|
|
||||||
|
logger.debug(f"calculated '{len(media_with_hashes)}' perceptual hashes for {url=}: {media_with_hashes}")
|
||||||
|
|
||||||
def calculate_pdq_hash(self, filename):
|
def calculate_pdq_hash(self, filename):
|
||||||
# returns a hexadecimal string with the perceptual hash for the given filename
|
# returns a hexadecimal string with the perceptual hash for the given filename
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ class WaybackArchiverEnricher(Enricher, Archiver):
|
|||||||
def configs() -> dict:
|
def configs() -> dict:
|
||||||
return {
|
return {
|
||||||
"timeout": {"default": 15, "help": "seconds to wait for successful archive confirmation from wayback, if more than this passes the result contains the job_id so the status can later be checked manually."},
|
"timeout": {"default": 15, "help": "seconds to wait for successful archive confirmation from wayback, if more than this passes the result contains the job_id so the status can later be checked manually."},
|
||||||
|
"if_not_archived_within": {"default": None, "help": "only tell wayback to archive if no archive is available before the number of seconds specified, use None to ignore this option. For more information: https://docs.google.com/document/d/1Nsv52MvSjbLb2PCpHlat0gkzw0EvtSgpKHu4mk0MnrA"},
|
||||||
"key": {"default": None, "help": "wayback API key. to get credentials visit https://archive.org/account/s3.php"},
|
"key": {"default": None, "help": "wayback API key. to get credentials visit https://archive.org/account/s3.php"},
|
||||||
"secret": {"default": None, "help": "wayback API secret. to get credentials visit https://archive.org/account/s3.php"}
|
"secret": {"default": None, "help": "wayback API secret. to get credentials visit https://archive.org/account/s3.php"}
|
||||||
}
|
}
|
||||||
@@ -50,7 +51,11 @@ class WaybackArchiverEnricher(Enricher, Archiver):
|
|||||||
"Accept": "application/json",
|
"Accept": "application/json",
|
||||||
"Authorization": f"LOW {self.key}:{self.secret}"
|
"Authorization": f"LOW {self.key}:{self.secret}"
|
||||||
}
|
}
|
||||||
r = requests.post('https://web.archive.org/save/', headers=ia_headers, data={'url': url})
|
post_data = {'url': url}
|
||||||
|
if self.if_not_archived_within:
|
||||||
|
post_data["if_not_archived_within"] = self.if_not_archived_within
|
||||||
|
# see https://docs.google.com/document/d/1Nsv52MvSjbLb2PCpHlat0gkzw0EvtSgpKHu4mk0MnrA for more options
|
||||||
|
r = requests.post('https://web.archive.org/save/', headers=ia_headers, data=post_data)
|
||||||
|
|
||||||
if r.status_code != 200:
|
if r.status_code != 200:
|
||||||
logger.error(em := f"Internet archive failed with status of {r.status_code}: {r.json()}")
|
logger.error(em := f"Internet archive failed with status of {r.status_code}: {r.json()}")
|
||||||
|
|||||||
@@ -18,17 +18,18 @@ class WhisperEnricher(Enricher):
|
|||||||
def __init__(self, config: dict) -> None:
|
def __init__(self, config: dict) -> None:
|
||||||
# without this STEP.__init__ is not called
|
# without this STEP.__init__ is not called
|
||||||
super().__init__(config)
|
super().__init__(config)
|
||||||
|
assert type(self.api_endpoint) == str and len(self.api_endpoint) > 0, "please provide a value for the whisper_enricher api_endpoint"
|
||||||
assert type(self.api_key) == str and len(self.api_key) > 0, "please provide a value for the whisper_enricher api_key"
|
assert type(self.api_key) == str and len(self.api_key) > 0, "please provide a value for the whisper_enricher api_key"
|
||||||
self.timeout = int(self.timeout)
|
self.timeout = int(self.timeout)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def configs() -> dict:
|
def configs() -> dict:
|
||||||
return {
|
return {
|
||||||
"api_endpoint": {"default": "https://whisper.spoettel.dev/api/v1", "help": "WhisperApi api endpoint"},
|
"api_endpoint": {"default": None, "help": "WhisperApi api endpoint, eg: https://whisperbox-api.com/api/v1, a deployment of https://github.com/bellingcat/whisperbox-transcribe."},
|
||||||
"api_key": {"default": None, "help": "WhisperApi api key for authentication"},
|
"api_key": {"default": None, "help": "WhisperApi api key for authentication"},
|
||||||
"include_srt": {"default": False, "help": "Whether to include a subtitle SRT (SubRip Subtitle file) for the video (can be used in video players)."},
|
"include_srt": {"default": False, "help": "Whether to include a subtitle SRT (SubRip Subtitle file) for the video (can be used in video players)."},
|
||||||
"timeout": {"default": 90, "help": "How many seconds to wait at most for a successful job completion."},
|
"timeout": {"default": 90, "help": "How many seconds to wait at most for a successful job completion."},
|
||||||
"action": {"default": "translation", "help": "which Whisper operation to execute", "choices": ["transcript", "translation", "language_detection"]},
|
"action": {"default": "translate", "help": "which Whisper operation to execute", "choices": ["transcribe", "translate", "language_detection"]},
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,9 +57,12 @@ class WhisperEnricher(Enricher):
|
|||||||
|
|
||||||
for i, m in enumerate(to_enrich.media):
|
for i, m in enumerate(to_enrich.media):
|
||||||
if m.is_video() or m.is_audio():
|
if m.is_video() or m.is_audio():
|
||||||
job_id = to_enrich.media[i].get("whisper_model")["job_id"]
|
job_id = to_enrich.media[i].get("whisper_model", {}).get("job_id")
|
||||||
|
if not job_id: continue
|
||||||
to_enrich.media[i].set("whisper_model", {
|
to_enrich.media[i].set("whisper_model", {
|
||||||
"job_id": job_id,
|
"job_id": job_id,
|
||||||
|
"job_status_check": f"{self.api_endpoint}/jobs/{job_id}",
|
||||||
|
"job_artifacts_check": f"{self.api_endpoint}/jobs/{job_id}/artifacts",
|
||||||
**(job_results[job_id] if job_results[job_id] else {"result": "incomplete or failed job"})
|
**(job_results[job_id] if job_results[job_id] else {"result": "incomplete or failed job"})
|
||||||
})
|
})
|
||||||
# append the extracted text to the content of the post so it gets written to the DBs like gsheets text column
|
# append the extracted text to the content of the post so it gets written to the DBs like gsheets text column
|
||||||
@@ -76,6 +80,7 @@ class WhisperEnricher(Enricher):
|
|||||||
"type": self.action,
|
"type": self.action,
|
||||||
# "language": "string" # may be a config
|
# "language": "string" # may be a config
|
||||||
}
|
}
|
||||||
|
logger.debug(f"calling API with {payload=}")
|
||||||
response = requests.post(f'{self.api_endpoint}/jobs', json=payload, headers={'Authorization': f'Bearer {self.api_key}'})
|
response = requests.post(f'{self.api_endpoint}/jobs', json=payload, headers={'Authorization': f'Bearer {self.api_key}'})
|
||||||
assert response.status_code == 201, f"calling the whisper api {self.api_endpoint} returned a non-success code: {response.status_code}"
|
assert response.status_code == 201, f"calling the whisper api {self.api_endpoint} returned a non-success code: {response.status_code}"
|
||||||
logger.debug(response.json())
|
logger.debug(response.json())
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ No URL available for {{ m.key }}.
|
|||||||
<a href="https://lens.google.com/uploadbyurl?url={{ url | quote }}">Google Lens</a>,
|
<a href="https://lens.google.com/uploadbyurl?url={{ url | quote }}">Google Lens</a>,
|
||||||
<a href="https://yandex.ru/images/touch/search?rpt=imageview&url={{ url | quote }}">Yandex</a>,
|
<a href="https://yandex.ru/images/touch/search?rpt=imageview&url={{ url | quote }}">Yandex</a>,
|
||||||
<a href="https://www.bing.com/images/search?view=detailv2&iss=sbi&form=SBIVSP&sbisrc=UrlPaste&q=imgurl:{{ url | quote }}">Bing</a>,
|
<a href="https://www.bing.com/images/search?view=detailv2&iss=sbi&form=SBIVSP&sbisrc=UrlPaste&q=imgurl:{{ url | quote }}">Bing</a>,
|
||||||
<a href="https://www.tineye.com/search/?url={{ url | quote }}">Tineye</a>,
|
<a href="https://www.tineye.com/search/?url={{ url | quote }}">Tineye</a>
|
||||||
</div>
|
</div>
|
||||||
<p></p>
|
<p></p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -52,6 +52,19 @@ class UrlUtil:
|
|||||||
# telegram
|
# telegram
|
||||||
if "https://telegram.org/img/emoji/" in url: return False
|
if "https://telegram.org/img/emoji/" in url: return False
|
||||||
|
|
||||||
|
# youtube
|
||||||
|
if "https://www.youtube.com/s/gaming/emoji/" in url: return False
|
||||||
|
if "https://yt3.ggpht.com" in url and "default-user=" in url: return False
|
||||||
|
if "https://www.youtube.com/s/search/audio/" in url: return False
|
||||||
|
|
||||||
|
# ok
|
||||||
|
if " https://ok.ru/res/i/" in url: return False
|
||||||
|
|
||||||
|
# vk
|
||||||
|
if "https://vk.com/emoji/" in url: return False
|
||||||
|
if "vk.com/images/" in url: return False
|
||||||
|
if "vk.com/images/reaction/" in url: return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ _MAJOR = "0"
|
|||||||
_MINOR = "6"
|
_MINOR = "6"
|
||||||
# On main and in a nightly release the patch should be one ahead of the last
|
# On main and in a nightly release the patch should be one ahead of the last
|
||||||
# released build.
|
# released build.
|
||||||
_PATCH = "2"
|
_PATCH = "4"
|
||||||
# This is mainly for nightly builds which have the suffix ".dev$DATE". See
|
# This is mainly for nightly builds which have the suffix ".dev$DATE". See
|
||||||
# https://semver.org/#is-v123-a-semantic-version for the semantics.
|
# https://semver.org/#is-v123-a-semantic-version for the semantics.
|
||||||
_SUFFIX = ""
|
_SUFFIX = ""
|
||||||
|
|||||||
Reference in New Issue
Block a user