tubesync/app/sync/matching.py

387 lines
17 KiB
Python

'''
Match functions take a single Media object instance as its only argument and return
two boolean values. The first value is if the match was exact or "best fit", the
second argument is the ID of the format that was matched.
'''
from django.conf import settings
min_height = getattr(settings, 'VIDEO_HEIGHT_CUTOFF', 360)
fallback_hd_cutoff = getattr(settings, 'VIDEO_HEIGHT_IS_HD', 500)
def get_best_combined_format(media):
'''
Attempts to see if there is a single, combined audio and video format that
exactly matches the source requirements. This is used over separate audio
and video formats if possible. Combined formats are the easiest to check
for as they must exactly match the source profile be be valid.
'''
for fmt in media.iter_formats():
# Check height matches
if media.source.source_resolution.strip().upper() != fmt['format']:
continue
# Check the video codec matches
if media.source.source_vcodec != fmt['vcodec']:
continue
# Check the audio codec matches
if media.source.source_acodec != fmt['acodec']:
continue
# if the source prefers 60fps, check for it
if media.source.prefer_60fps:
if not fmt['is_60fps']:
continue
# If the source prefers HDR, check for it
if media.source.prefer_hdr:
if not fmt['is_hdr']:
continue
# If we reach here, we have a combined match!
return True, fmt['id']
return False, False
def get_best_audio_format(media):
'''
Finds the best match for the source required audio format. If the source
has a 'fallback' of fail this can return no match.
'''
# Order all audio-only formats by bitrate
audio_formats = []
for fmt in media.iter_formats():
# If the format has a video stream, skip it
if fmt['vcodec'] is not None:
continue
audio_formats.append(fmt)
audio_formats = list(reversed(sorted(audio_formats, key=lambda k: k['abr'])))
if not audio_formats:
# Media has no audio formats at all
return False, False
# Find the highest bitrate audio format with a matching codec
for fmt in audio_formats:
if media.source.source_acodec == fmt['acodec']:
# Matched!
return True, fmt['id']
# No codecs matched
if media.source.can_fallback:
# Can fallback, find the next highest bitrate non-matching codec
return False, audio_formats[0]
else:
# Can't fallback
return False, False
def get_best_video_format(media):
'''
Finds the best match for the source required video format. If the source
has a 'fallback' of fail this can return no match. Resolution is treated
as the most important factor to match. This is pretty verbose due to the
'soft' matching requirements for prefer_hdr and prefer_60fps.
'''
# Check if the source wants audio only, fast path to return
if media.source.is_audio:
return False, False
# Filter video-only formats by resolution that matches the source
video_formats = []
for fmt in media.iter_formats():
# If the format has an audio stream, skip it
if fmt['acodec'] is not None:
continue
if media.source.source_resolution.strip().upper() == fmt['format']:
video_formats.append(fmt)
# Check we matched some streams
if not video_formats:
# No streams match the requested resolution, see if we can fallback
if media.source.can_fallback:
# Find the next-best format matches by height
for fmt in media.iter_formats():
# If the format has an audio stream, skip it
if fmt['acodec'] is not None:
continue
if (fmt['height'] <= media.source.source_resolution_height and
fmt['height'] >= min_height):
video_formats.append(fmt)
else:
# Can't fallback
return False, False
video_formats = list(reversed(sorted(video_formats, key=lambda k: k['height'])))
source_resolution = media.source.source_resolution.strip().upper()
source_vcodec = media.source.source_vcodec
if not video_formats:
# Still no matches
return False, False
exact_match, best_match = None, None
# Of our filtered video formats, check for resolution + codec + hdr + fps match
if media.source.prefer_60fps and media.source.prefer_hdr:
for fmt in video_formats:
# Check for an exact match
if (source_resolution == fmt['format'] and
source_vcodec == fmt['vcodec'] and
fmt['is_hdr'] and
fmt['is_60fps']):
# Exact match
exact_match, best_match = True, fmt
break
if media.source.can_fallback:
if not best_match:
for fmt in video_formats:
# Check for a resolution, hdr and fps match but drop the codec
if (source_resolution == fmt['format'] and
fmt['is_hdr'] and fmt['is_60fps']):
# Close match
exact_match, best_match = False, fmt
break
if not best_match:
for fmt in video_formats:
# Check for a codec, hdr and fps match but drop the resolution
if (source_vcodec == fmt['vcodec'] and
fmt['is_hdr'] and fmt['is_60fps']):
# Close match
exact_match, best_match = False, fmt
break
if not best_match:
for fmt in video_formats:
# Check for resolution, codec and 60fps match
if (source_resolution == fmt['format'] and
source_vcodec == fmt['vcodec'] and
fmt['is_60fps']):
exact_match, best_match = False, fmt
break
if not best_match:
for fmt in video_formats:
# Check for resolution and hdr match
if (source_resolution == fmt['format'] and
fmt['is_hdr']):
exact_match, best_match = False, fmt
break
if not best_match:
for fmt in video_formats:
# Check for resolution and 60fps match
if (source_resolution == fmt['format'] and
fmt['is_60fps']):
exact_match, best_match = False, fmt
break
if not best_match:
for fmt in video_formats:
# Check for resolution, codec and hdr match
if (source_resolution == fmt['format'] and
source_vcodec == fmt['vcodec'] and
fmt['is_hdr']):
exact_match, best_match = False, fmt
break
if not best_match:
for fmt in video_formats:
# Check for resolution and codec
if (source_resolution == fmt['format'] and
source_vcodec == fmt['vcodec']):
exact_match, best_match = False, fmt
break
if not best_match:
for fmt in video_formats:
# Check for resolution
if source_resolution == fmt['format']:
exact_match, best_match = False, fmt
break
if not best_match:
# Match the highest resolution
exact_match, best_match = False, video_formats[0]
# Check for resolution + codec + fps match
if media.source.prefer_60fps and not media.source.prefer_hdr:
for fmt in video_formats:
# Check for an exact match
if (source_resolution == fmt['format'] and
source_vcodec == fmt['vcodec'] and
fmt['is_60fps'] and
not fmt['is_hdr']):
# Exact match
exact_match, best_match = True, fmt
break
if media.source.can_fallback:
if not best_match:
for fmt in video_formats:
# Check for a resolution and fps match but drop the codec
if (source_resolution == fmt['format'] and
fmt['is_60fps'] and
not fmt['is_hdr']):
exact_match, best_match = False, fmt
break
if not best_match:
for fmt in video_formats:
# Check for a codec and fps match but drop the resolution
if (source_vcodec == fmt['vcodec'] and
fmt['is_60fps'] and
not fmt['is_hdr']):
exact_match, best_match = False, fmt
break
if not best_match:
for fmt in video_formats:
# Check for a codec and 60fps match
if (source_vcodec == fmt['vcodec'] and
fmt['is_60fps']):
exact_match, best_match = False, fmt
break
if not best_match:
for fmt in video_formats:
# Check for codec and resolution match bot drop 60fps
if (source_resolution == fmt['format'] and
source_vcodec == fmt['vcodec'] and
not fmt['is_hdr']):
exact_match, best_match = False, fmt
break
if not best_match:
for fmt in video_formats:
# Check for codec and resolution match only
if (source_resolution == fmt['format'] and
source_vcodec == fmt['vcodec']):
exact_match, best_match = False, fmt
break
if not best_match:
for fmt in video_formats:
# Check for resolution
if source_resolution == fmt['format']:
exact_match, best_match = False, fmt
break
if not best_match:
# Match the highest resolution
exact_match, best_match = False, video_formats[0]
# Check for resolution + codec + hdr
elif media.source.prefer_hdr and not media.source.prefer_60fps:
for fmt in video_formats:
# Check for an exact match
if (source_resolution == fmt['format'] and
source_vcodec == fmt['vcodec'] and
fmt['is_hdr']):
# Exact match
exact_match, best_match = True, fmt
break
if media.source.can_fallback:
if not best_match:
for fmt in video_formats:
# Check for a resolution and fps match but drop the codec
if (source_resolution == fmt['format'] and
fmt['is_hdr'] and
not fmt['is_60fps']):
exact_match, best_match = False, fmt
break
if not best_match:
for fmt in video_formats:
# Check for a codec and fps match but drop the resolution
if (source_vcodec == fmt['vcodec'] and
fmt['is_hdr'] and
not fmt['is_60fps']):
exact_match, best_match = False, fmt
break
if not best_match:
for fmt in video_formats:
# Check for a codec and 60fps match
if (source_vcodec == fmt['vcodec'] and
fmt['is_hdr']):
exact_match, best_match = False, fmt
break
if not best_match:
for fmt in video_formats:
# Check for codec and resolution match bot drop hdr
if (source_resolution == fmt['format'] and
source_vcodec == fmt['vcodec'] and
not fmt['is_60fps']):
exact_match, best_match = False, fmt
break
if not best_match:
for fmt in video_formats:
# Check for codec and resolution match only
if (source_resolution == fmt['format'] and
source_vcodec == fmt['vcodec']):
exact_match, best_match = False, fmt
break
if not best_match:
for fmt in video_formats:
# Check for resolution
if source_resolution == fmt['format']:
exact_match, best_match = False, fmt
break
if not best_match:
# Match the highest resolution
exact_match, best_match = False, video_formats[0]
# check for resolution + codec
elif not media.source.prefer_hdr and not media.source.prefer_60fps:
for fmt in video_formats:
# Check for an exact match
if (source_resolution == fmt['format'] and
source_vcodec == fmt['vcodec'] and
not fmt['is_60fps'] and
not fmt['is_hdr']):
# Exact match
exact_match, best_match = True, fmt
break
if media.source.can_fallback:
if not best_match:
for fmt in video_formats:
# Check for a resolution, hdr and fps match but drop the codec
if (source_resolution == fmt['format'] and
not fmt['is_hdr'] and not fmt['is_60fps']):
# Close match
exact_match, best_match = False, fmt
break
if not best_match:
for fmt in video_formats:
# Check for a codec, hdr and fps match but drop the resolution
if (source_vcodec == fmt['vcodec'] and
not fmt['is_hdr'] and fmt['is_60fps']):
# Close match
exact_match, best_match = False, fmt
break
if not best_match:
for fmt in video_formats:
# Check for resolution, codec and hdr match
if (source_resolution == fmt['format'] and
source_vcodec == fmt['vcodec'] and
not fmt['is_hdr']):
exact_match, best_match = False, fmt
break
if not best_match:
for fmt in video_formats:
# Check for resolution, codec and 60fps match
if (source_resolution == fmt['format'] and
source_vcodec == fmt['vcodec'] and
not fmt['is_60fps']):
exact_match, best_match = False, fmt
break
if not best_match:
for fmt in video_formats:
# Check for resolution and codec
if (source_resolution == fmt['format'] and
source_vcodec == fmt['vcodec']):
exact_match, best_match = False, fmt
break
if not best_match:
for fmt in video_formats:
# Check for resolution and not hdr
if (source_resolution == fmt['format'] and
not fmt['is_hdr']):
exact_match, best_match = False, fmt
break
if not best_match:
for fmt in video_formats:
# Check for resolution
if source_resolution == fmt['format']:
exact_match, best_match = False, fmt
break
if not best_match:
# Match the highest resolution
exact_match, best_match = False, video_formats[0]
# See if we found a match
if best_match:
# Final check to see if the match we found was good enough
if exact_match:
return True, best_match['id']
elif media.source.can_fallback:
# Allow the fallback if it meets requirements
if (media.source.fallback == media.source.FALLBACK_NEXT_BEST_HD and
best_match['height'] >= fallback_hd_cutoff):
return False, best_match['id']
elif media.source.fallback == media.source.FALLBACK_NEXT_BEST:
return False, best_match['id']
# Nope, failed to find match
return False, False