python-ring-doorbell icon indicating copy to clipboard operation
python-ring-doorbell copied to clipboard

Cannot download ring videos

Open SergiuCip opened this issue 1 year ago • 10 comments

When trying to download a video i get the following error: Got error: RingError('HTTP error with status code 404 during query of url https://api.ring.com/clients_api/dings/

Please help

SergiuCip avatar Feb 17 '24 11:02 SergiuCip

Can you provide a bit more information. How are you trying to download it? Do other queries work? Maybe post some logs etc?

sdb9696 avatar Feb 17 '24 12:02 sdb9696

Sure, So I can run the following commands: ring-doorbell list, ring-doorbell motion-detection --device-name "DEVICENAME" --on, ring-doorbell history --device-name "Front Door" but when I try running the following command ring-doorbell videos --download-all command, I get the following error:

❯ ring-doorbell videos --download-all
---------------------------------
Ring CLI
	Getting videos linked on your Ring account.
	This may take some time....

	Downloading 70 videos linked on your Ring account.
	This may take some time....

	1/70 Downloading xxxx.mp4
Got error: RingError('HTTP error with status code 404 during query of url https://api.ring.com/clients_api/dings/xxxx/recording: 404 Client Error: Not Found for url: https://share-service-download-bucket.s3.amazonaws.com/xxxxx.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=xxxxSignedHeaders=host&X-Amz-Signature=xxxx')

Traceback (most recent call last):
  File "/Users/sergiu/miniforge3/lib/python3.10/site-packages/ring_doorbell/auth.py", line 163, in query
    resp.raise_for_status()
  File "/Users/sergiu/miniforge3/lib/python3.10/site-packages/requests/models.py", line 1021, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 404 Client Error: Not Found for url: https://share-service-download-bucket.s3.amazonaws.com/xxxmp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/sergiu/miniforge3/lib/python3.10/site-packages/ring_doorbell/cli.py", line 47, in __call__
    asyncio.run(self.main(*args, **kwargs))
  File "/Users/sergiu/miniforge3/lib/python3.10/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/Users/sergiu/miniforge3/lib/python3.10/asyncio/base_events.py", line 649, in run_until_complete
    return future.result()
  File "/Users/sergiu/miniforge3/lib/python3.10/site-packages/asyncclick/core.py", line 1120, in main
    rv = await self.invoke(ctx)
  File "/Users/sergiu/miniforge3/lib/python3.10/site-packages/asyncclick/core.py", line 1739, in invoke
    return await _process_result(await sub_ctx.command.invoke(sub_ctx))
  File "/Users/sergiu/miniforge3/lib/python3.10/site-packages/asyncclick/core.py", line 1485, in invoke
    return await ctx.invoke(self.callback, **ctx.params)
  File "/Users/sergiu/miniforge3/lib/python3.10/site-packages/asyncclick/core.py", line 824, in invoke
    rv = await rv
  File "/Users/sergiu/miniforge3/lib/python3.10/site-packages/asyncclick/core.py", line 824, in invoke
    rv = await rv
  File "/Users/sergiu/miniforge3/lib/python3.10/site-packages/ring_doorbell/cli.py", line 584, in videos
    device.recording_download(event["id"], filename=filename, override=False)
  File "/Users/sergiu/miniforge3/lib/python3.10/site-packages/ring_doorbell/doorbot.py", line 387, in recording_download
    req = self._ring.query(url, timeout=timeout)
  File "/Users/sergiu/miniforge3/lib/python3.10/site-packages/ring_doorbell/ring.py", line 142, in query
    return self._query(url, method, extra_params, data, json, timeout)
  File "/Users/sergiu/miniforge3/lib/python3.10/site-packages/ring_doorbell/ring.py", line 155, in _query
    response = self.auth.query(
  File "/Users/sergiu/miniforge3/lib/python3.10/site-packages/ring_doorbell/auth.py", line 165, in query
    raise RingError(
ring_doorbell.exceptions.RingError: HTTP error with status code 404 during query of url https://api.ring.com/clients_api/dings/xxxx/recording: 404 Client Error: Not Found for url: https://share-service-download-bucket.s3.amazonaws.com/

unless python3 isn't supported?

SergiuCip avatar Feb 17 '24 16:02 SergiuCip

Encountering the same error now using Python 3.8 (albeit a different endpoint) - yesterday it worked fine:

ring_doorbell.exceptions.RingError: HTTP error with status code 404 during query of url 
https://api.ring.com/clients_api/doorbots/[...]/history: 404 Client Error: Not Found for url: 
https://api.ring.com/clients_api/doorbots/[...]/history?limit=50&older_than=[...]

This is thrown when invoking the recording_download() function only, in my case. Other functions work fine and provide results properly, like update_data() and devices().

Related #242

5E7EN avatar Feb 19 '24 03:02 5E7EN

I have noticed the same thing using the cli ring-doorbell videos --download

By manually clicking on the link after getting the 404, I realized that it works, and if i re-run the same cli command, the first video then downloads and the 2nd fails, and this can be repeated for the rest of the videos.

I believe what's happening is the download process is an async process. It takes a few moments for the file to get exported to the s3 bucket before it is actually available to download.

chrisnicholls avatar Apr 29 '24 11:04 chrisnicholls

Same issue

dakkusingh avatar Jun 11 '24 10:06 dakkusingh

I believe what's happening is the download process is an async process. It takes a few moments for the file to get exported to the s3 bucket before it is actually available to download.

I have noticed the same thing using the cli ring-doorbell videos --download

By manually clicking on the link after getting the 404, I realized that it works, and if i re-run the same cli command, the first video then downloads and the 2nd fails, and this can be repeated for the rest of the videos.

I believe what's happening is the download process is an async process. It takes a few moments for the file to get exported to the s3 bucket before it is actually available to download.

Its actually nothing to do with AWS record / save time.

if you try to download older videos by manually specifying the event ID, you would run into same issue.

example

devices = ring.video_devices()

event_ids = [1234, 5678]

for eid in event_ids:
    devices[0].recording_download(eid, filename)

dakkusingh avatar Jun 11 '24 18:06 dakkusingh

That's not a contradiction to what I said. The ring server still needs to process the (albeit old) video file to S3 for sharing.

On Tue, Jun 11, 2024, 9:27 PM Dakku @.***> wrote:

I believe what's happening is the download process is an async process. It takes a few moments for the file to get exported to the s3 bucket before it is actually available to download.

I have noticed the same thing using the cli ring-doorbell videos --download

By manually clicking on the link after getting the 404, I realized that it works, and if i re-run the same cli command, the first video then downloads and the 2nd fails, and this can be repeated for the rest of the videos.

I believe what's happening is the download process is an async process. It takes a few moments for the file to get exported to the s3 bucket before it is actually available to download.

Its actually nothing to do with AWS record / save time.

if you try to download older videos by manually specifying the event ID, you would run into same issue.

example

devices = ring.video_devices()

event_ids = [1234, 5678]

for eid in event_ids: devices[0].recording_download(eid, filename)

— Reply to this email directly, view it on GitHub https://github.com/tchellomello/python-ring-doorbell/issues/350#issuecomment-2161369120, or unsubscribe https://github.com/notifications/unsubscribe-auth/AH33UMO7O2DG567VNFEKMCDZG46SFAVCNFSM6AAAAABDNHZRYGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDCNRRGM3DSMJSGA . You are receiving this because you commented.Message ID: @.***>

5E7EN avatar Jun 13 '24 17:06 5E7EN

I had the same issue - I solved it by making it wait 20 seconds until it tries the same file again. It usually works after one or 2 retries

    try_again = True
    while try_again:
        try:
            frontdoor.recording_download(id_value, filename=filename, override=True)
            try_again = False
        except RingError as e:
            if '404' in str(e):
                print(f"File for ID {id_value} not found. Wait for 20 seconds and retrying...")
                time.sleep(20)
            else:
                print(f"An error occurred: {e}")
                time.sleep(10)
                try_again = False

guivernl avatar Jun 19 '24 15:06 guivernl

I tried the time limit and still couldnt get it to work.

I had to roll my own download function using:

video_url = device.recording_url(event["id"])

and you can manually download the unbranded video without times burnt into it!

dakkusingh avatar Jun 19 '24 15:06 dakkusingh

I had the same problem retrying fixed it. Here's a script to download all videos:

import time
import os
from pathlib import Path
import json
import requests
import getpass
from ring_doorbell import Auth, Ring, RingError, Requires2FAError
from datetime import datetime

# Define the base output directory
BASE_OUTPUT_DIR = Path("ring_output")

def token_updated(token):
    cache_file.write_text(json.dumps(token))

def otp_callback():
    return input("2FA code: ")

# Initialize Ring object
cache_file = Path("RingVideoDownloader.token.cache")
auth = None

if cache_file.is_file():
    auth = Auth("RingVideoDownloader/1.0", json.loads(cache_file.read_text()), token_updated)
else:
    username = input("Enter your Ring username: ")
    password = getpass.getpass("Enter your Ring password: ")
    auth = Auth("RingVideoDownloader/1.0", None, token_updated)
    try:
        auth.fetch_token(username, password)
    except Requires2FAError:
        print("2FA is enabled. You will receive a code via email or text.")
        try:
            auth.fetch_token(username, password, otp_callback())
        except Exception as e:
            print(f"2FA Authentication failed: {e}")
            exit(1)
    except Exception as e:
        print(f"Authentication failed: {e}")
        exit(1)

ring = Ring(auth)
ring.update_data()

def ensure_output_dir(device_name, date_str):
    output_dir = BASE_OUTPUT_DIR / device_name / date_str
    if not output_dir.exists():
        output_dir.mkdir(parents=True)
        print(f"Created output directory: {output_dir}")
    return output_dir

def download_video(device, event, max_retries=3):
    created_at = event.get('created_at', datetime.now().isoformat())
    if isinstance(created_at, str):
        date_str = created_at.split('T')[0]  # Extract date part
        created_at = created_at.replace(':', '-')
    else:
        date_str = created_at.date().isoformat()
        created_at = created_at.isoformat().replace(':', '-')
    
    output_dir = ensure_output_dir(device.name, date_str)
    
    kind = event.get('kind', 'unknown')
    
    filename = output_dir / f"{device.name}_{created_at}_{kind}.mp4"
    
    for attempt in range(max_retries):
        try:
            device.recording_download(event['id'], filename=str(filename), override=True)
            print(f"Successfully downloaded: {filename}")
            return True
        except RingError as e:
            if '404' in str(e):
                print(f"Video for ID {event['id']} not found (404 error). Attempt {attempt + 1}/{max_retries}")
            else:
                print(f"An error occurred: {e}. Attempt {attempt + 1}/{max_retries}")
            
            if attempt < max_retries - 1:
                print(f"Waiting 20 seconds before retrying...")
                time.sleep(20)
            else:
                print(f"Failed to download video after {max_retries} attempts.")
                return False
    return False

def main():
    devices = ring.devices()
    
    all_devices = []
    all_devices.extend(devices.doorbells)
    all_devices.extend(devices.stickup_cams)
    
    for device in all_devices:
        print(f"Downloading videos for {device.name}")
        
        events = device.history(limit=100)  # Adjust the limit as needed
        print(f"Total events for {device.name}: {len(events)}")
        
        successful_downloads = 0
        failed_downloads = 0
        
        for index, event in enumerate(events, 1):
            print(f"Attempting to download video {index}/{len(events)}")
            if download_video(device, event):
                successful_downloads += 1
            else:
                failed_downloads += 1
                print("Skipping to next video...")
        
        print(f"Download summary for {device.name}:")
        print(f"Successfully downloaded: {successful_downloads}")
        print(f"Failed to download: {failed_downloads}")
        print(f"Videos saved in device and date-specific folders under: {BASE_OUTPUT_DIR}")

if __name__ == "__main__":
    main()

ceramicwhite avatar Jul 29 '24 20:07 ceramicwhite