diff --git a/.gitignore b/.gitignore index f5cb607a7..b0c32fdc0 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ _trial_temp/ /tests/.coverage.* /lbry/wallet/bin +/venv diff --git a/lbry/extras/daemon/daemon.py b/lbry/extras/daemon/daemon.py index b62e5ff56..33518a75d 100644 --- a/lbry/extras/daemon/daemon.py +++ b/lbry/extras/daemon/daemon.py @@ -2198,6 +2198,7 @@ class Daemon(metaclass=JSONRPCServerType): [--reposted_claim_id=] [--reposted=] [--claim_type=] [--stream_types=...] [--media_types=...] [--fee_currency=] [--fee_amount=] + [--duration=] [--any_tags=...] [--all_tags=...] [--not_tags=...] [--any_languages=...] [--all_languages=...] [--not_languages=...] @@ -2284,6 +2285,8 @@ class Daemon(metaclass=JSONRPCServerType): --media_types= : (list) filter by 'video/mp4', 'image/png', etc --fee_currency= : (string) specify fee currency: LBC, BTC, USD --fee_amount= : (decimal) content download fee (supports equality constraints) + --duration= : (int) duration of video or audio in seconds + (supports equality constraints) --any_tags= : (list) find claims containing any of the tags --all_tags= : (list) find claims containing every tag --not_tags= : (list) find claims not containing any of these tags diff --git a/lbry/wallet/server/db/reader.py b/lbry/wallet/server/db/reader.py index a8daf229c..7fd4ca1f3 100644 --- a/lbry/wallet/server/db/reader.py +++ b/lbry/wallet/server/db/reader.py @@ -37,7 +37,7 @@ ATTRIBUTE_ARRAY_MAX_LENGTH = 100 INTEGER_PARAMS = { 'height', 'creation_height', 'activation_height', 'expiration_height', - 'timestamp', 'creation_timestamp', 'release_time', 'fee_amount', + 'timestamp', 'creation_timestamp', 'duration', 'release_time', 'fee_amount', 'tx_position', 'channel_join', 'reposted', 'amount', 'effective_amount', 'support_amount', 'trending_group', 'trending_mixed', @@ -404,7 +404,6 @@ def _get_referenced_rows(censor: Censor, txo_rows: List[dict]): # channels must come first for client side inflation to work properly return channel_txos + reposted_txos - @measure def search(constraints) -> Tuple[List, List, int, int, Censor]: assert set(constraints).issubset(SEARCH_PARAMS), \ diff --git a/lbry/wallet/server/db/writer.py b/lbry/wallet/server/db/writer.py index b70810b07..a8199b608 100644 --- a/lbry/wallet/server/db/writer.py +++ b/lbry/wallet/server/db/writer.py @@ -63,6 +63,7 @@ class SQLDB: media_type text, fee_amount integer default 0, fee_currency text, + duration integer, -- reposts reposted_claim_hash bytes, @@ -140,6 +141,7 @@ class SQLDB: create unique index if not exists claim_type_effective_amount_idx on claim (claim_type, effective_amount, claim_hash); create unique index if not exists channel_hash_release_time_idx on claim (channel_hash, release_time, claim_hash); + create unique index if not exists filter_stream_duration_idx on claim (duration, trending_global, trending_mixed, claim_hash); -- TODO: verify that all indexes below are used create index if not exists claim_height_normalized_idx on claim (height, normalized asc); @@ -318,6 +320,7 @@ class SQLDB: 'title': None, 'description': None, 'author': None, + 'duration': None, 'claim_type': None, 'stream_type': None, 'media_type': None, @@ -341,6 +344,10 @@ class SQLDB: claim_record['title'] = claim.stream.title claim_record['description'] = claim.stream.description claim_record['author'] = claim.stream.author + if claim.stream.video and claim.stream.video.duration: + claim_record['duration'] = claim.stream.video.duration + if claim.stream.audio and claim.stream.audio.duration: + claim_record['duration'] = claim.stream.audio.duration if claim.stream.release_time: claim_record['release_time'] = claim.stream.release_time if claim.stream.has_fee: @@ -374,12 +381,12 @@ class SQLDB: INSERT OR IGNORE INTO claim ( claim_hash, claim_id, claim_name, normalized, txo_hash, tx_position, amount, claim_type, media_type, stream_type, timestamp, creation_timestamp, - fee_currency, fee_amount, title, description, author, height, reposted_claim_hash, + fee_currency, fee_amount, title, description, author, duration, height, reposted_claim_hash, creation_height, release_time, activation_height, expiration_height, short_url) VALUES ( :claim_hash, :claim_id, :claim_name, :normalized, :txo_hash, :tx_position, :amount, :claim_type, :media_type, :stream_type, :timestamp, :timestamp, - :fee_currency, :fee_amount, :title, :description, :author, :height, :reposted_claim_hash, :height, + :fee_currency, :fee_amount, :title, :description, :author, :duration, :height, :reposted_claim_hash, :height, CASE WHEN :release_time IS NOT NULL THEN :release_time ELSE :timestamp END, CASE WHEN :normalized NOT IN (SELECT normalized FROM claimtrie) THEN :height END, CASE WHEN :height >= 137181 THEN :height+2102400 ELSE :height+262974 END, @@ -397,7 +404,7 @@ class SQLDB: txo_hash=:txo_hash, tx_position=:tx_position, amount=:amount, height=:height, claim_type=:claim_type, media_type=:media_type, stream_type=:stream_type, timestamp=:timestamp, fee_amount=:fee_amount, fee_currency=:fee_currency, - title=:title, description=:description, author=:author, reposted_claim_hash=:reposted_claim_hash, + title=:title, duration=:duration, description=:description, author=:author, reposted_claim_hash=:reposted_claim_hash, release_time=CASE WHEN :release_time IS NOT NULL THEN :release_time ELSE release_time END WHERE claim_hash=:claim_hash; """, claims) diff --git a/tests/integration/blockchain/test_claim_commands.py b/tests/integration/blockchain/test_claim_commands.py index fd6bdefd9..2a883f7a4 100644 --- a/tests/integration/blockchain/test_claim_commands.py +++ b/tests/integration/blockchain/test_claim_commands.py @@ -73,6 +73,12 @@ class ClaimSearchCommand(ClaimTestCase): (result['txid'], result['claim_id']) ) + async def assertFindsNoClaims(self, **kwargs): + kwargs.setdefault('order_by', ['height', '^name']) + results = await self.claim_search(**kwargs) + print(results) + self.assertEqual(0, len(results)) + async def test_basic_claim_search(self): await self.create_channel() channel_txo = self.channel['outputs'][0] @@ -328,6 +334,13 @@ class ClaimSearchCommand(ClaimTestCase): await self.assertFindsClaims([image], media_types=['image/png']) await self.assertFindsClaims([image, video], media_types=['video/mp4', 'image/png']) + # duration + await self.assertFindsClaim(video, duration=f'>{14}') + await self.assertFindsClaim(video, duration=f'<{16}') + await self.assertFindsClaim(video, duration=15) + await self.assertFindsNoClaims(duration=f'>{100}') + await self.assertFindsNoClaims(duration=f'<{14}') + async def test_search_by_text(self): chan1_id = self.get_claim_id(await self.channel_create('@SatoshiNakamoto')) chan2_id = self.get_claim_id(await self.channel_create('@Bitcoin'))