I re-ran find_orphans.py and logged the output deleted several thousand lines of record names.
Code: Select all
[ljohnson@KISE-055 ~]$ cat /usr/bin/find_orphans.py | tee -a find_orphans_test.txt
#!/usr/bin/python3
from MythTV import MythDB, MythBE, Recorded, MythError
from socket import timeout
import os
import sys
def human_size(s):
s = float(s)
o = 0
while s > 1000:
s /= 1000
o += 1
return str(round(s,1))+('B ','KB','MB','GB','TB')[o]
class File( str ):
def __new__(self, host, group, path, name, size):
return str.__new__(self, name)
def __init__(self, host, group, path, name, size):
self.host = host
self.group = group
self.path = path
self.size = int(size)
def pprint(self):
name = '%s: %s' % (self.host, os.path.join(self.path, self))
print(' {0:<90}{1:>8}'.format(name, human_size(self.size)))
def delete(self):
be = MythBE(self.host, db=DB)
be.deleteFile(self, self.group)
class MyRecorded( Recorded ):
_table = 'recorded'
def pprint(self):
name = '{0.hostname}: {0.title}'.format(self)
if self.subtitle:
name += ' - '+self.subtitle
print(' {0:<70}{1:>28}'.format(name,self.basename))
def printrecs(title, recs):
print(title)
for rec in sorted(recs, key=lambda x: x.title):
rec.pprint()
print('{0:>88}{1:>12}'.format('Count:',len(recs)))
def printfiles(title, files):
print(title)
for f in sorted(files, key=lambda x: x.path):
f.pprint()
size = sum([f.size for f in files])
print('{0:>88}{1:>12}'.format('Total:',human_size(size)))
def populate(host=None):
unfiltered = []
kwargs = {'livetv':True}
if host:
with DB as c:
c.execute("""SELECT count(1) FROM settings
WHERE hostname=%s AND value=%s""",
(host, 'BackendServerAddr'))
if c.fetchone()[0] == 0:
raise Exception('Invalid hostname specified on command line.')
hosts = [host]
kwargs['hostname'] = host
else:
with DB as c:
c.execute("""SELECT hostname FROM settings
WHERE value='BackendServerAddr'""")
hosts = [r[0] for r in c.fetchall()]
for host in hosts:
for sg in DB.getStorageGroup():
if sg.groupname in ('Videos','Banners','Coverart',\
'Fanart','Screenshots','Trailers'):
continue
try:
dirs,files,sizes = BE.getSGList(host, sg.groupname, sg.dirname)
for f,s in zip(files,sizes):
newfile = File(host, sg.groupname, sg.dirname, f, s)
if newfile not in unfiltered:
unfiltered.append(newfile)
except:
pass
recs = list(DB.searchRecorded(**kwargs))
zerorecs = []
orphvids = []
for rec in list(recs):
if rec.basename in unfiltered:
recs.remove(rec)
i = unfiltered.index(rec.basename)
f = unfiltered.pop(i)
if f.size < 1024:
zerorecs.append(rec)
name = rec.basename.rsplit('.',1)[0]
for f in list(unfiltered):
if name in f:
unfiltered.remove(f)
for f in list(unfiltered):
if not (f.endswith('.mpg') or f.endswith('.nuv') or f.endswith('.ts')):
continue
orphvids.append(f)
unfiltered.remove(f)
orphimgs = []
for f in list(unfiltered):
if not f.endswith('.png'):
continue
orphimgs.append(f)
unfiltered.remove(f)
dbbackup = []
for f in list(unfiltered):
if 'sql' not in f:
continue
dbbackup.append(f)
unfiltered.remove(f)
return (recs, zerorecs, orphvids, orphimgs, dbbackup, unfiltered)
def delete_recs(recs):
printrecs('The following recordings will be deleted', recs)
print('Are you sure you want to continue?')
try:
res = input('> ')
while True:
if res == 'yes':
for rec in recs:
rec.delete(True, True)
break
elif res == 'no':
break
else:
res = input("'yes' or 'no' > ")
except MythError:
name = '{0.hostname}: {0.title}'.format(rec)
if rec.subtitle:
name += ' - '+rec.subtitle
print("Warning: Failed to delete '" + name + "'")
except KeyboardInterrupt:
pass
except EOFError:
sys.exit(0)
def delete_files(files):
printfiles('The following files will be deleted', files)
print('Are you sure you want to continue?')
try:
res = input('> ')
while True:
if res == 'yes':
for f in files:
f.delete()
break
elif res == 'no':
break
else:
res = input("'yes' or 'no' > ")
except KeyboardInterrupt:
pass
except EOFError:
sys.exit(0)
def main(host=None):
while True:
recs, zerorecs, orphvids, orphimgs, dbbackup, unfiltered = populate(host)
if len(recs):
printrecs("Recordings with missing files", recs)
if len(zerorecs):
printrecs("Zero byte recordings", zerorecs)
if len(orphvids):
printfiles("Orphaned video files", orphvids)
if len(orphimgs):
printfiles("Orphaned snapshots", orphimgs)
if len(dbbackup):
printfiles("Database backups", dbbackup)
if len(unfiltered):
printfiles("Other files", unfiltered)
opts = []
if len(recs):
opts.append(['Delete orphaned recording entries', delete_recs, recs])
if len(zerorecs):
opts.append(['Delete zero byte recordings', delete_recs, zerorecs])
if len(orphvids):
opts.append(['Delete orphaned video files', delete_files, orphvids])
if len(orphimgs):
opts.append(['Delete orphaned snapshots', delete_files, orphimgs])
if len(unfiltered):
opts.append(['Delete other files', delete_files, unfiltered])
opts.append(['Refresh list', None, None])
print('Please select from the following')
for i, opt in enumerate(opts):
print(' {0}. {1}'.format(i+1, opt[0]))
try:
inner = True
res = input('> ')
while inner:
try:
res = int(res)
except:
res = input('input number. ctrl-c to exit > ')
continue
if (res <= 0) or (res > len(opts)):
res = input('input number within range > ')
continue
break
opt = opts[res-1]
if opt[1] is None:
continue
else:
opt[1](opt[2])
except KeyboardInterrupt:
break
except EOFError:
sys.exit(0)
DB = MythDB()
BE = MythBE(db=DB)
DB.searchRecorded.handler = MyRecorded
DB.searchRecorded.dbclass = MyRecorded
if __name__ == '__main__':
if len(sys.argv) == 2:
main(sys.argv[1])
else:
main()
#!/usr/bin/python3
from MythTV import MythDB, MythBE, Recorded, MythError
from socket import timeout
import os
import sys
def human_size(s):
s = float(s)
o = 0
while s > 1000:
s /= 1000
o += 1
return str(round(s,1))+('B ','KB','MB','GB','TB')[o]
class File( str ):
def __new__(self, host, group, path, name, size):
return str.__new__(self, name)
def __init__(self, host, group, path, name, size):
self.host = host
self.group = group
self.path = path
self.size = int(size)
def pprint(self):
name = '%s: %s' % (self.host, os.path.join(self.path, self))
print(' {0:<90}{1:>8}'.format(name, human_size(self.size)))
def delete(self):
be = MythBE(self.host, db=DB)
be.deleteFile(self, self.group)
class MyRecorded( Recorded ):
_table = 'recorded'
def pprint(self):
name = '{0.hostname}: {0.title}'.format(self)
if self.subtitle:
name += ' - '+self.subtitle
print(' {0:<70}{1:>28}'.format(name,self.basename))
def printrecs(title, recs):
print(title)
for rec in sorted(recs, key=lambda x: x.title):
rec.pprint()
print('{0:>88}{1:>12}'.format('Count:',len(recs)))
def printfiles(title, files):
print(title)
for f in sorted(files, key=lambda x: x.path):
f.pprint()
size = sum([f.size for f in files])
print('{0:>88}{1:>12}'.format('Total:',human_size(size)))
def populate(host=None):
unfiltered = []
kwargs = {'livetv':True}
if host:
with DB as c:
c.execute("""SELECT count(1) FROM settings
WHERE hostname=%s AND value=%s""",
(host, 'BackendServerAddr'))
if c.fetchone()[0] == 0:
raise Exception('Invalid hostname specified on command line.')
hosts = [host]
kwargs['hostname'] = host
else:
with DB as c:
c.execute("""SELECT hostname FROM settings
WHERE value='BackendServerAddr'""")
hosts = [r[0] for r in c.fetchall()]
for host in hosts:
for sg in DB.getStorageGroup():
if sg.groupname in ('Videos','Banners','Coverart',\
'Fanart','Screenshots','Trailers'):
continue
try:
dirs,files,sizes = BE.getSGList(host, sg.groupname, sg.dirname)
for f,s in zip(files,sizes):
newfile = File(host, sg.groupname, sg.dirname, f, s)
if newfile not in unfiltered:
unfiltered.append(newfile)
except:
pass
recs = list(DB.searchRecorded(**kwargs))
zerorecs = []
orphvids = []
for rec in list(recs):
if rec.basename in unfiltered:
recs.remove(rec)
i = unfiltered.index(rec.basename)
f = unfiltered.pop(i)
if f.size < 1024:
zerorecs.append(rec)
name = rec.basename.rsplit('.',1)[0]
for f in list(unfiltered):
if name in f:
unfiltered.remove(f)
for f in list(unfiltered):
if not (f.endswith('.mpg') or f.endswith('.nuv') or f.endswith('.ts')):
continue
orphvids.append(f)
unfiltered.remove(f)
orphimgs = []
for f in list(unfiltered):
if not f.endswith('.png'):
continue
orphimgs.append(f)
unfiltered.remove(f)
dbbackup = []
for f in list(unfiltered):
if 'sql' not in f:
continue
dbbackup.append(f)
unfiltered.remove(f)
return (recs, zerorecs, orphvids, orphimgs, dbbackup, unfiltered)
def delete_recs(recs):
printrecs('The following recordings will be deleted', recs)
print('Are you sure you want to continue?')
try:
res = input('> ')
while True:
if res == 'yes':
for rec in recs:
rec.delete(True, True)
break
elif res == 'no':
break
else:
res = input("'yes' or 'no' > ")
except MythError:
name = '{0.hostname}: {0.title}'.format(rec)
if rec.subtitle:
name += ' - '+rec.subtitle
print("Warning: Failed to delete '" + name + "'")
except KeyboardInterrupt:
pass
except EOFError:
sys.exit(0)
def delete_files(files):
printfiles('The following files will be deleted', files)
print('Are you sure you want to continue?')
try:
res = input('> ')
while True:
if res == 'yes':
for f in files:
f.delete()
break
elif res == 'no':
break
else:
res = input("'yes' or 'no' > ")
except KeyboardInterrupt:
pass
except EOFError:
sys.exit(0)
def main(host=None):
while True:
recs, zerorecs, orphvids, orphimgs, dbbackup, unfiltered = populate(host)
if len(recs):
printrecs("Recordings with missing files", recs)
if len(zerorecs):
printrecs("Zero byte recordings", zerorecs)
if len(orphvids):
printfiles("Orphaned video files", orphvids)
if len(orphimgs):
printfiles("Orphaned snapshots", orphimgs)
if len(dbbackup):
printfiles("Database backups", dbbackup)
if len(unfiltered):
printfiles("Other files", unfiltered)
opts = []
if len(recs):
opts.append(['Delete orphaned recording entries', delete_recs, recs])
if len(zerorecs):
opts.append(['Delete zero byte recordings', delete_recs, zerorecs])
if len(orphvids):
opts.append(['Delete orphaned video files', delete_files, orphvids])
if len(orphimgs):
opts.append(['Delete orphaned snapshots', delete_files, orphimgs])
if len(unfiltered):
opts.append(['Delete other files', delete_files, unfiltered])
opts.append(['Refresh list', None, None])
print('Please select from the following')
for i, opt in enumerate(opts):
print(' {0}. {1}'.format(i+1, opt[0]))
try:
inner = True
res = input('> ')
while inner:
try:
res = int(res)
except:
res = input('input number. ctrl-c to exit > ')
continue
if (res <= 0) or (res > len(opts)):
res = input('input number within range > ')
continue
break
opt = opts[res-1]
if opt[1] is None:
continue
else:
opt[1](opt[2])
except KeyboardInterrupt:
break
except EOFError:
sys.exit(0)
DB = MythDB()
BE = MythBE(db=DB)
DB.searchRecorded.handler = MyRecorded
DB.searchRecorded.dbclass = MyRecorded
if __name__ == '__main__':
if len(sys.argv) == 2:
main(sys.argv[1])
else:
main()
[ljohnson@KISE-055 ~]$ /usr/bin/find_orphans.py | tee -a find_orphans_test.txt
Recordings with missing files
KISE-055: 60 Minutes 1131_20190107020000.ts
KISE-055: A Year in Space 1061_20171117220000.ts
......
KISE-055: Young Sheldon - A Broom Closet and Satan's Monopoly Board 1131_20191004020000.ts
KISE-055: Young Sheldon - Hobbitses, Physicses and a Ball With Zip 1131_20191018020000.ts
Count: 1468
Orphaned video files
kise-055: /pvr/default/10301_20200608230000.ts 6.6GB
kise-055: /pvr/default/10301_20200804011700.ts 1.2GB
kise-055: /pvr/default/10603_20200608223000.ts 476.4MB
kise-055: /pvr/default/11301_20200609000000.ts 3.4GB
kise-055: /pvr/default/11301_20200609003000.ts 3.3GB
kise-055: /pvr/default/1131_20190819230000.ts 2.3GB
kise-055: /pvr/default/1131_20200220150000.ts 7.6GB
kise-055: /pvs/livetv/1401_20190818232750.ts 0.0B
Total: 24.9GB
Orphaned snapshots
kise-055: /pvr/default/10301_20200608230000.ts.png 2.0MB
kise-055: /pvr/default/10603_20200608223000.ts.png 210.5KB
kise-055: /pvr/default/11301_20200609000000.ts.png 1.2MB
kise-055: /pvr/default/11301_20200609003000.ts.png 274.3KB
Total: 3.7MB
Database backups
kise-055: /pvr/db_bu/mythconverg-1361-20200910050502.sql.gz 150.1MB
kise-055: /pvr/db_bu/mythconverg-1361-20200911050502.sql.gz 151.4MB
kise-055: /pvr/db_bu/mythconverg-1361-20200914050502.sql.gz 150.2MB
kise-055: /pvr/db_bu/mythconverg-20200913050501.sql 0.0B
Total: 451.7MB
Other files
kise-055: /pvr/default/test 0.0B
Total: 0.0B
Please select from the following
1. Delete orphaned recording entries
2. Delete orphaned video files
3. Delete orphaned snapshots
4. Delete other files
5. Refresh list
> The following recordings will be deleted
KISE-055: 60 Minutes 1131_20190107020000.ts
KISE-055: A Year in Space 1061_20171117220000.ts
............
KISE-055: Young Sheldon - A Broom Closet and Satan's Monopoly Board 1131_20191004020000.ts
KISE-055: Young Sheldon - Hobbitses, Physicses and a Ball With Zip 1131_20191018020000.ts
Count: 1468
Are you sure you want to continue?
> yes
Traceback (most recent call last):
File "/usr/bin/find_orphans.py", line 230, in <module>
main()
File "/usr/bin/find_orphans.py", line 214, in main
opt[1](opt[2])
File "/usr/bin/find_orphans.py", line 129, in delete_recs
rec.delete(True, True)
File "/usr/lib/python3.8/site-packages/MythTV/dataheap.py", line 377, in delete
return self.getProgram().delete(force, rerecord)
File "/usr/lib/python3.8/site-packages/MythTV/mythproto.py", line 972, in delete
res = int(be.deleteRecording(self, force=force))
File "/usr/lib/python3.8/site-packages/MythTV/mythproto.py", line 668, in deleteRecording
[command,program.toString()]))
File "/usr/lib/python3.8/site-packages/MythTV/mythproto.py", line 962, in toString
return BACKEND_SEP.join(self._deprocess())
File "/usr/lib/python3.8/site-packages/MythTV/altdict.py", line 178, in _deprocess
data[i] = self._inv_trans[self._field_type[i]](v)
File "/usr/lib/python3.8/site-packages/MythTV/altdict.py", line 113, in <lambda>
lambda x: str(int(x.timestamp())),
File "/usr/lib/python3.8/site-packages/MythTV/utility/dt.py", line 481, in timestamp
return ((utc_naive - utc_epoch).total_seconds())
TypeError: can't subtract offset-naive and offset-aware datetimes
I don't know python and could use some help from someone.