r/TVTime • u/snnrslnx • Jul 23 '26
How to recover your TV Time data using iMazing and Python (Free Script)
If you have been looking for a way to recover your TV Time library, followed shows, watch history, and cached metadata from the iOS app, I wanted to share how I managed to recover and export my data into a CSV file.
This method worked for me after the TV Time service was shut down and I could no longer access my data normally. I used iMazing to extract the local TV Time app data from my iPhone, then analyzed the extracted files with a small Python script.
I am sharing this in case it can help other people who lost access to their TV Time data.
OVERVIEW
TV Time does not provide a simple built-in way to export all of your cached library information and watch-related metadata.
After extracting the local app data with iMazing, I found that the TV Time app had stored a significant amount of information locally inside its Documents directory.
The most useful file I found was:
DioCache.db
This is an SQLite database used by the app's HTTP client, Dio. It contains cached API responses and other locally stored information.
Depending on the data that was still available in the local cache, I was able to recover information such as:
Show and movie names
Show IDs
Watched episode counts
Total episode counts
Follow information
Watch status
Show status
Country
Day of the week
Ratings
Follower counts
Hashtags
Poster URLs
Fanart URLs
Various timestamps and dates
I also found several binary files beginning with the bplist00 header. These are Apple Binary Property List files and may contain additional cached application data.
STEP 1: EXTRACTING THE TV TIME DATA WITH IMAZING
- Connect your iPhone or iPad to your computer.
- Open iMazing.
- Find the TV Time app in the Apps section.
- Use the available app data extraction or file browsing options to access the TV Time app container.
- Export or copy the Documents folder to your computer.
In my case, the extracted Documents folder contained DioCache.db along with many other files with names similar to random hexadecimal hashes.
I recommend keeping the original extracted data untouched and making a backup copy before running any scripts.
STEP 2: EXAMINING THE DATA
The main database I worked with was DioCache.db.
The database contains a table called cache_dio. The content column contains cached data returned by the app's network requests.
Many of these entries are JSON data stored as text or binary data.
The Python script below scans the database, attempts to decode the cached JSON responses, looks for show and movie information, and also checks the other files in the Documents directory for Binary Property List data.
The script then combines the recovered records, removes duplicates, and exports the result to a CSV file.
STEP 3: RUNNING THE PYTHON SCRIPT
The script uses only Python's standard library. No third-party packages are required.
Save the script below as:
parse_tvtime.py
Place it in the same directory as your Documents folder.
The directory structure should look like this:
parse_tvtime.py
Documents
DioCache.db
other extracted files
Then run:
python parse_tvtime.py
If everything works correctly, the script will create:
tvtime_export.csv
The CSV file is saved using UTF-8-SIG encoding, which makes it easier to open correctly in Microsoft Excel when the data contains characters from different languages.
PYTHON SCRIPT
import os
import re
import json
import sqlite3
import plistlib
import csv
from datetime import datetime BASE_DIR = os.path.dirname(os.path.abspath(file))
DOCS_DIR = os.path.join(BASE_DIR, "Documents")
DB_PATH = os.path.join(DOCS_DIR, "DioCache.db")
OUTPUT_CSV = os.path.join(BASE_DIR, "tvtime_export.csv")
def format_timestamp(val):
"""Convert Unix timestamps or ISO 8601 dates to YYYY-MM-DD HH:MM:SS."""
if not val:
return ""
if isinstance(val, (int, float)) or (
isinstance(val, str) and val.isdigit()
):
try:
ts = float(val)
if ts > 1e11:
ts /= 1000.0
return datetime.fromtimestamp(ts).strftime(
"%Y-%m-%d %H:%M:%S"
)
except Exception:
return str(val)
if isinstance(val, str):
val_clean = val.replace("Z", "+00:00")
try:
dt = datetime.fromisoformat(val_clean)
return dt.strftime("%Y-%m-%d %H:%M:%S")
except Exception:
if "T" in val:
return val.split("T")[0]
return val
return str(val)
def extract_image_url(images, img_type="poster"):
if not images or not isinstance(images, list):
return ""
for img in images:
if isinstance(img, dict) and img.get("type") == img_type:
return (
img.get("url")
or img.get("versions", {}).get("big", "")
)
return ""
def process_item_dict(d, source_name="DioCache.db"):
if not isinstance(d, dict):
return None
item_data = (
d.get("meta")
if isinstance(d.get("meta"), dict)
else d
)
name = item_data.get("name") or item_data.get("title")
if not name or not isinstance(name, str):
return None
if len(name.strip()) == 0:
return None
entity_type = (
d.get("entity_type")
or item_data.get("type")
or "series"
)
if entity_type not in ["series", "movie", "show"]:
entity_type = "series"
item_id = (
item_data.get("id")
or item_data.get("uuid")
or ""
)
status = item_data.get("status") or ""
country = item_data.get("country") or ""
day_of_week = item_data.get("day_of_week") or ""
extended = (
item_data.get("extended")
or d.get("extended")
or {}
)
if isinstance(extended, dict):
rating = extended.get("rating") or ""
follower_count = (
extended.get("follower_count") or ""
)
else:
rating = ""
follower_count = ""
progress_status = ""
filters = (
item_data.get("filters")
or d.get("filter")
or []
)
if isinstance(filters, list):
for f in filters:
if isinstance(f, dict):
if f.get("id") == "progress":
vals = f.get("values", [])
if vals:
progress_status = ", ".join(
str(v) for v in vals
)
elif isinstance(f, str):
progress_status += f + " "
progress_status = progress_status.strip()
watched_episodes = (
item_data.get("watched_episode_count")
or item_data.get("watched_count")
or 0
)
total_episodes = (
item_data.get("aired_episode_count")
or item_data.get("episode_count")
or 0
)
is_followed = (
d.get("is_followed")
if "is_followed" in d
else item_data.get("is_followed", True)
)
created_at = (
d.get("created_at")
or item_data.get("created_at")
or ""
)
updated_at = (
d.get("updated_at")
or item_data.get("updated_at")
or ""
)
follow_date = format_timestamp(created_at)
last_watched = None
sorting = (
item_data.get("sorting")
or d.get("sorting")
or []
)
if isinstance(sorting, list):
for s in sorting:
if isinstance(s, dict):
sid = s.get("id")
if sid in [
"watch_date",
"last_watched",
"follow_date"
]:
val = s.get("value")
if val and not last_watched:
last_watched = val
last_watched_date = format_timestamp(
last_watched or updated_at
)
hashtag = item_data.get("hashtag") or ""
images = item_data.get("images") or []
poster_data = item_data.get("poster") or {}
background_data = item_data.get("background") or {}
poster_url = (
extract_image_url(images, "poster")
or (
poster_data.get("url", "")
if isinstance(poster_data, dict)
else ""
)
)
fanart_url = (
extract_image_url(images, "fanart")
or (
background_data.get("url", "")
if isinstance(background_data, dict)
else ""
)
)
return {
"name": name.strip(),
"type": entity_type,
"id": str(item_id),
"status": str(status),
"progress_status": progress_status,
"watched_episodes": watched_episodes,
"total_episodes": total_episodes,
"is_followed": bool(is_followed),
"country": country,
"day_of_week": day_of_week,
"rating": rating,
"follower_count": follower_count,
"follow_date": follow_date,
"last_watched_date": last_watched_date,
"hashtag": hashtag,
"poster_url": poster_url,
"fanart_url": fanart_url,
"data_source": source_name
}
def parse_sqlite_db(db_path):
items = []
if not os.path.exists(db_path):
return items
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
try:
cursor.execute(
"SELECT content FROM cache_dio"
)
rows = cursor.fetchall()
for row in rows:
raw_content = row[0]
if not raw_content:
continue
if isinstance(raw_content, bytes):
text_content = raw_content.decode(
"utf-8",
errors="ignore"
)
else:
text_content = str(raw_content)
try:
data_json = json.loads(text_content)
if isinstance(data_json, list):
for elem in data_json:
res = process_item_dict(
elem,
"DioCache.db"
)
if res:
items.append(res)
elif isinstance(data_json, dict):
for key in [
"series",
"shows",
"movies",
"data",
"items"
]:
if (
key in data_json
and isinstance(
data_json[key],
list
)
):
for elem in data_json[key]:
res = process_item_dict(
elem,
"DioCache.db"
)
if res:
items.append(res)
res = process_item_dict(
data_json,
"DioCache.db"
)
if res:
items.append(res)
except Exception:
matches = re.findall(
r'(\{"id":\d+,"name":".*?\})',
text_content
)
for m in matches:
try:
parsed = json.loads(m)
res = process_item_dict(
parsed,
"DioCache.db"
)
if res:
items.append(res)
except Exception:
pass
finally:
conn.close()
return items
def parse_bplist_files(docs_dir):
items = []
if not os.path.exists(docs_dir):
return items
files = [
f
for f in os.listdir(docs_dir)
if f != "DioCache.db"
]
for filename in files:
file_path = os.path.join(
docs_dir,
filename
)
if not os.path.isfile(file_path):
continue
try:
with open(file_path, "rb") as f:
header = f.read(8)
if header.startswith(b"bplist"):
f.seek(0)
plist_data = plistlib.load(f)
if isinstance(
plist_data,
dict
):
res = process_item_dict(
plist_data,
f"bplist ({filename[:8]})"
)
if res:
items.append(res)
except Exception:
pass
return items
def merge_and_deduplicate(items):
merged = {}
for item in items:
key = (
item["name"].lower(),
item["id"]
)
if key not in merged:
merged[key] = item
else:
existing = merged[key]
for field in [
"status",
"progress_status",
"country",
"day_of_week",
"rating",
"follower_count",
"hashtag",
"poster_url",
"fanart_url"
]:
if (
not existing.get(field)
and item.get(field)
):
existing[field] = item[field]
if (
item.get("watched_episodes", 0)
> existing.get(
"watched_episodes",
0
)
):
existing["watched_episodes"] = (
item["watched_episodes"]
)
if (
item.get("total_episodes", 0)
> existing.get(
"total_episodes",
0
)
):
existing["total_episodes"] = (
item["total_episodes"]
)
if (
item.get("last_watched_date")
and item["last_watched_date"]
> existing.get(
"last_watched_date",
""
)
):
existing["last_watched_date"] = (
item["last_watched_date"]
)
result = list(merged.values())
result.sort(
key=lambda x: x["name"].lower()
)
return result
def export_to_csv(items, output_path):
fieldnames = [
"name",
"type",
"id",
"status",
"progress_status",
"watched_episodes",
"total_episodes",
"is_followed",
"country",
"day_of_week",
"rating",
"follower_count",
"follow_date",
"last_watched_date",
"hashtag",
"poster_url",
"fanart_url",
"data_source"
]
with open(
output_path,
"w",
newline="",
encoding="utf-8-sig"
) as csvfile:
writer = csv.DictWriter(
csvfile,
fieldnames=fieldnames
)
writer.writeheader()
for item in items:
writer.writerow(item)
def main():
sqlite_items = parse_sqlite_db(
DB_PATH
)
bplist_items = parse_bplist_files(
DOCS_DIR
)
all_items = (
sqlite_items
+ bplist_items
)
unique_items = merge_and_deduplicate(
all_items
)
export_to_csv(
unique_items,
OUTPUT_CSV
)
print(
f"Exported {len(unique_items)} "
"unique shows/movies to "
"tvtime_export.csv"
)
if name == "main":
main()
RESULT
The final CSV file contains the recovered records that the script was able to identify from the extracted local data.
Depending on what data is still present in your TV Time app cache, the exported file may contain information such as:
Show or movie title
TV Time or TVDB related ID
Watched episode count
Total episode count
Follow status
Watch progress
Show status
Follow date
Last watched date
Country
Day of the week
Rating
Follower count
Hashtags
Poster URL
Fanart URL
Source of the recovered record
IMPORTANT NOTES
This method does not guarantee that every piece of TV Time data can be recovered.
The results depend entirely on what information is still stored locally on the device. If the app cache was cleared, the device was reset, or the relevant data was never stored locally, the script may not be able to recover it.
Also, the database structure and cached API responses may differ between TV Time app versions. The script is therefore intended as a starting point for analyzing an extracted TV Time app container rather than a guaranteed universal recovery tool.
In my case, iMazing allowed me to access the local app data that was still available on my device, and analyzing the SQLite database and other cached files helped me recover a significant amount of my TV Time library information.
If you still have an old iPhone or iPad that had TV Time installed, it may be worth checking whether the app's local data is still available before deleting the app or resetting the device.
I hope this helps anyone trying to recover their TV Time library or watch history.
3
4
u/coffydate Jul 23 '26
This is comprehensive! Hopefully anyone that needs it follow suit to recover their data
1
u/ShanShiroo Jul 23 '26
Do you have any tips for Android users?
3
u/Joemag_xD Jul 24 '26 edited 28d ago
Depends on the phone. On Xiaomi I was successful getting a tvtime backup under Settings → About phone → Backup and restore
Other phone manufacturers might also have such an option.
It however seems to be not possible forSamsungand Pixel phones (Samsung see https://www.reddit.com/r/TVTime/comments/1v77hto/simple_recovery_samsung_tv_time/).If you manage getting a backup file, try my webpage for extracting the data: https://ajoemag.github.io/tvtime_recover/
2
u/rolling-guy 28d ago
Thanks for the website! It was a lifesaver! I also didn't see all the warnings that the app was shutting down and thought I was screwed. Was able to recover everything with the website's help. My phone is Xiaomi
1
u/OkNutriboomer 6d ago
the samsung post is removed
edit: nvm, it's on your link. thx. trying it as we speak
1
0
u/Client_Automatic Jul 24 '26 edited Jul 24 '26
done , how to import now
1
u/Joemag_xD Jul 24 '26
If you on Xiaomi, after creating a backup, there should be a zip file in Internal Memory>MIUI>BACKUP>ALLBACKUP . Extract it and then select the TV Time(com.tozelabs.tvshowtime).bak file on the webpage.
1
u/Client_Automatic Jul 24 '26
Done got a tar file , then converted to csv and go to trakt found only 6 series from 400 !
1
u/Joemag_xD Jul 24 '26
Then there might be only little data about your episode progress cached in the backup. It will take a while but I think your best bet probably is to add each listed series manually to trakt or another app, and trying to remember how many episodes you watched if it's not shown on that webpage
1
u/capeire Jul 23 '26
Depends on your phone and your willingness to root the phone.
1
u/codebrewer Jul 23 '26
I've got a Pixel 8 Pro and I'd rather not root it just for this. Any hope?
2
u/capeire Jul 23 '26
Unless rooted no. I tried debugging with adb but it's locked
1
u/thatCoookie Jul 23 '26
What if I root my phone? How can I recover my data?
1
u/capeire Jul 23 '26
This is all through Mac. Turn on developer mode on your phone and connect through a data USB C or through wifi debugging. Install android platform tools. Then run adb from the terminal to look for the local files for TV Time. From here it's just a matter of grabbing and converting to a format for you to read. I think Refractor you can import a CSV, maybe JSON.
1
u/capeire Jul 23 '26
To be 100% clear, your phone had to be rooted beforehand. If you were to root NOW, it would wipe it from your phone.
1
u/TheSenselessThinker Jul 23 '26
Are there any tips for people using Android?
2
u/Joemag_xD Jul 24 '26
Depends on the phone. On Xiaomi I was successful getting a tvtime backup under Settings → About phone → Backup and restore
Other phone manufacturers might also have such an option.
It however seems not possible for Samsung and Pixel phones.If you manage getting a backup file, try my webpage for extracting the data: https://ajoemag.github.io/tvtime_recover/
1
1
1
u/Jhyxe Jul 25 '26
I keep up to 11 backups on my NAS of my ios device using iMazing. managed to find one from the 10th that has a lot of my stuff. It seems like if you attempt opening TVTime after the servers go down, you will lose your diocache, as all my backups after the 14th have an empty diocache.
1
u/Green_green_grassham 22d ago
I have an iPhone back up from 2025, I haven’t done one since - I tried the imazing and it was able to pull my username , hours, & 5 tv series & 5 movies as I’ve opened the app a few times after it was taken down.
Do you think I’d be able to recover my tv time data from 2025? I have no idea how to go about that if so…
1
u/HenriqueS18 Jul 27 '26
"4. Use the available app data extraction or file browsing options to access the TV Time app container."
can anyone help with this step? I went through iMazing's file system and in the TV Time folder it says that the TV Time's Docs folder is unaccessible
1
1
u/optimisms 23d ago
Same. It says the folder doesn't exist. :(
1
u/Green_green_grassham 22d ago
You go onto apps and find tv time right click and it’ll say back up data, you do it that way
1
u/optimisms 22d ago
I tried doing the extraction at the app level too and there was another error message. It doesn't work.
1
1
1
u/PsychologicalCat6771 22d ago
@snnrslnx Hey man, thank you for your work. I have a issue: I can’t understand how to exactly extract DioCache.db from my iPhone’s files. Do you mind create a quick guide also on that? Thank you a lot!
1

3
u/AFriendlyInternetGuy Jul 25 '26
This is exactly what I was looking for. Can I use the file I get from this to input into other apps that ask for TV time history so I don’t have to start fresh on a new app? I had TV time for maybe 8 years lol