[vlc-commits] [Git][videolan/vlc][3.0.x] 12 commits: demux: avi: set frame flags on video_es only

Jean-Baptiste Kempf (@jbk) gitlab at videolan.org
Fri May 12 15:49:24 UTC 2023



Jean-Baptiste Kempf pushed to branch 3.0.x at VideoLAN / VLC


Commits:
fc7e4a2f by Francois Cartegnie at 2023-05-12T15:11:58+00:00
demux: avi: set frame flags on video_es only

(cherry picked from commit 4502f33c20e46f5f4837b47cd99e552d65f4b360)

- - - - -
dbcc0760 by Francois Cartegnie at 2023-05-12T15:11:58+00:00
demux: avi: account header outside of sample size

(cherry-picked from commit c8fe3d812c277821ade872e334e78acc19deaf03)
(cherry picked from commit 32705bd3675e8fb87eb3c5d827f91a5ae8d0bc3f)

- - - - -
8a561ea2 by Francois Cartegnie at 2023-05-12T15:11:58+00:00
demux: avi: move around code to ease understanding

(cherry picked from commit c6cc1df8e233818ca0ae1d423012f5099223ed3c)

- - - - -
91299bbc by Francois Cartegnie at 2023-05-12T15:11:58+00:00
demux: avi: remove unneeded temp var/refetch

(cherry picked from commit b354b0529a0c88f1cb48d21987801e537eafed38)

- - - - -
f1503017 by Francois Cartegnie at 2023-05-12T15:11:58+00:00
demux: avi: add comments

(cherry picked from commit 9836217e0ed6cb24a560c6dc42fda2b1af063085)

- - - - -
c8b6380b by Francois Cartegnie at 2023-05-12T15:11:58+00:00
demux: avi: simplify audio seek computation

(cherry picked from commit 58aba619c8a435c4a156404661b24a12d77d543c)

- - - - -
0a0ed889 by Francois Cartegnie at 2023-05-12T15:11:58+00:00
demux: avi: replace useless define

(cherry picked from commit 4ea55641013820e447c7a39eaf26c7ec0bdb3c73)

- - - - -
a8dc27d9 by Francois Cartegnie at 2023-05-12T15:11:58+00:00
demux: avi: replace redundant track lookups

(cherry picked from commit cfc233814e22e8c9e63ab983a0f1d2968cdd0bfb)

- - - - -
ccdc0322 by Francois Cartegnie at 2023-05-12T15:11:58+00:00
demux: avi: add proper check for index alloc

(cherry picked from commit 2bb108680bb633e9e695f93cd988be9fff0f44b6)

- - - - -
e2cc130f by Francois Cartegnie at 2023-05-12T15:11:58+00:00
demux: avi: return insert position on indexAppend

(cherry picked from commit 817e0d30c42c8ee3b245c7d9d5fd5cd7370989e8)

- - - - -
e61aa5e7 by Francois Cartegnie at 2023-05-12T15:11:58+00:00
demux: avi: fix bogus indexless sample reads

was always using the previous chunk size

refs #27425

(cherry picked from commit b108e622d9431bd9153658089adfa7cdcf5fb231)

- - - - -
f098271f by Francois Cartegnie at 2023-05-12T15:11:58+00:00
demux: avi: change index non fatal messages to warning

(cherry picked from commit bb629537b156c8ead0975c420fed1ccbcb55113a)

- - - - -


1 changed file:

- modules/demux/avi/avi.c


Changes:

=====================================
modules/demux/avi/avi.c
=====================================
@@ -137,7 +137,7 @@ typedef struct
 } avi_index_t;
 static void avi_index_Init( avi_index_t * );
 static void avi_index_Clean( avi_index_t * );
-static void avi_index_Append( avi_index_t *, uint64_t *, avi_entry_t * );
+static int64_t avi_index_Append( avi_index_t *, uint64_t *, avi_entry_t * );
 
 typedef struct
 {
@@ -210,11 +210,9 @@ static vlc_tick_t AVI_GetDPTS   ( avi_track_t *, int64_t i_count );
 static vlc_tick_t AVI_GetPTS    ( avi_track_t * );
 
 
-static int AVI_StreamChunkFind( demux_t *, unsigned int i_stream );
-static int AVI_StreamChunkSet ( demux_t *,
-                                unsigned int i_stream, unsigned int i_ck );
-static int AVI_StreamBytesSet ( demux_t *,
-                                unsigned int i_stream, uint64_t i_byte );
+static int AVI_StreamChunkFind( demux_t *, avi_track_t * );
+static int AVI_StreamChunkSet ( demux_t *, avi_track_t *, unsigned int i_ck );
+static int AVI_StreamBytesSet ( demux_t *, avi_track_t *, uint64_t i_byte );
 
 vlc_fourcc_t AVI_FourccGetCodec( unsigned int i_cat, vlc_fourcc_t );
 static int   AVI_GetKeyFlag    ( vlc_fourcc_t , uint8_t * );
@@ -988,26 +986,36 @@ error:
  *****************************************************************************/
 
 static block_t * ReadFrame( demux_t *p_demux, const avi_track_t *tk,
-                     const unsigned int i_header, const int i_size )
+                            uint32_t i_header, uint32_t i_osize )
 {
-    block_t *p_frame = vlc_stream_Block( p_demux->s, __EVEN( i_size ) );
-    if ( !p_frame ) return p_frame;
-
-    if( i_size % 2 )    /* read was padded on word boundary */
+    /* skip header */
+    if( i_header )
     {
-        p_frame->i_buffer--;
+        assert(i_header % 8 == 0);
+        ssize_t i_skip = vlc_stream_Read( p_demux->s, NULL, i_header );
+        if( i_skip < 0 || (size_t) i_skip < i_header )
+            return NULL;
     }
 
-    if( i_header >= p_frame->i_buffer || tk->i_width_bytes > INT32_MAX - 3 )
+    /* read size padded on word boundary */
+    uint32_t i_size = __EVEN(i_osize);
+
+    if( i_size == 0 )
+        return block_Alloc(0); /* vlc_stream_Block can't read/alloc 0 sized */
+
+    block_t *p_frame = vlc_stream_Block( p_demux->s, i_size );
+    if ( !p_frame )
+        return p_frame;
+
+    if( i_osize == i_size - 1 )
+        p_frame->i_buffer--;
+
+    if( tk->i_width_bytes > INT32_MAX - 3 )
     {
         p_frame->i_buffer = 0;
         return p_frame;
     }
 
-    /* skip header */
-    p_frame->p_buffer += i_header;
-    p_frame->i_buffer -= i_header;
-
     const unsigned int i_stride_bytes = (tk->i_width_bytes + 3) & ~3;
 
     if ( !tk->i_width_bytes || !i_stride_bytes )
@@ -1231,7 +1239,6 @@ static int Demux_Seekable( demux_t *p_demux )
         block_t         *p_frame;
         int64_t i_pos;
         unsigned int i;
-        size_t i_size;
 
         /* search for first chunk to be read */
         for( i = 0, b_done = true, i_pos = -1; i < p_sys->i_track; i++ )
@@ -1333,11 +1340,14 @@ static int Demux_Seekable( demux_t *p_demux )
                     index.i_pos    = avi_pk.i_pos;
                     index.i_length = avi_pk.i_size;
                     index.i_lengthtotal = index.i_length;
-                    avi_index_Append( &tk->idx, &p_sys->i_movi_lastchunk_pos, &index );
+                    int64_t i_indexid = avi_index_Append( &tk->idx, &p_sys->i_movi_lastchunk_pos, &index );
 
                     /* do we will read this data ? */
-                    if( AVI_GetDPTS( tk, toread[i_track].i_toread ) > -p_sys->i_read_increment )
+                    if( i_indexid >= 0 &&
+                        AVI_GetDPTS( tk, toread[i_track].i_toread ) > -p_sys->i_read_increment )
                     {
+                        tk->i_idxposc = (unsigned int) i_indexid;
+                        tk->i_idxposb = 0;
                         break;
                     }
                     else
@@ -1361,11 +1371,16 @@ static int Demux_Seekable( demux_t *p_demux )
         /* Set the track to use */
         tk = p_sys->track[i_track];
 
+        size_t i_size;
+        unsigned i_ck_remaining_bytes = tk->idx.p_entry[tk->i_idxposc].i_length -
+                                        tk->i_idxposb;
+
         /* read those data */
         if( tk->i_samplesize )
         {
             int64_t i_toread;
 
+            /* remaining bytes to read inside the current read increment */
             if( ( i_toread = toread[i_track].i_toread ) <= 0 )
             {
                 if( tk->i_samplesize > 1 )
@@ -1374,26 +1389,23 @@ static int Demux_Seekable( demux_t *p_demux )
                 }
                 else
                 {
+                    /* refill current read increment */
                     i_toread = AVI_PTSToByte( tk, 20 * 1000 );
                     i_toread = __MAX( i_toread, 100 );
                 }
             }
-            i_size = __MIN( tk->idx.p_entry[tk->i_idxposc].i_length -
-                                tk->i_idxposb,
-                            (size_t) i_toread );
+            i_size = __MIN( i_ck_remaining_bytes, (size_t) i_toread );
         }
         else
         {
-            i_size = tk->idx.p_entry[tk->i_idxposc].i_length;
+            assert(tk->i_idxposb == 0);
+            i_size = i_ck_remaining_bytes;
         }
 
-        if( tk->i_idxposb == 0 )
-        {
-            i_size += 8; /* need to read and skip header */
-        }
+        /* need to read and skip tag/header */
+        const uint8_t i_header = ( tk->i_idxposb == 0 ) ? 8 : 0;
 
-        if( ( p_frame = ReadFrame( p_demux, tk,
-                        ( tk->i_idxposb == 0 ) ? 8 : 0, i_size ) )==NULL )
+        if( ( p_frame = ReadFrame( p_demux, tk, i_header, i_size ) )==NULL )
         {
             msg_Warn( p_demux, "failed reading data" );
             tk->b_eof = false;
@@ -1406,18 +1418,14 @@ static int Demux_Seekable( demux_t *p_demux )
         {
             p_frame->i_flags = BLOCK_FLAG_TYPE_I;
         }
-        else
+        else if( tk->fmt.i_cat == VIDEO_ES )
         {
             p_frame->i_flags = BLOCK_FLAG_TYPE_PB;
         }
 
-        /* read data */
+        /* advance chunk/byte pointers */
         if( tk->i_samplesize )
         {
-            if( tk->i_idxposb == 0 )
-            {
-                i_size -= 8;
-            }
             toread[i_track].i_toread -= i_size;
             tk->i_idxposb += i_size;
             if( tk->i_idxposb >=
@@ -1427,18 +1435,18 @@ static int Demux_Seekable( demux_t *p_demux )
                 tk->i_idxposc++;
             }
         }
-        else
+        else /* full chunk */
         {
-            int i_length = tk->idx.p_entry[tk->i_idxposc].i_length;
-
+            /* Goto to next chunk */
             tk->i_idxposc++;
             if( tk->fmt.i_cat == AUDIO_ES )
             {
-                tk->i_blockno += tk->i_blocksize > 0 ? ( i_length + tk->i_blocksize - 1 ) / tk->i_blocksize : 1;
+                tk->i_blockno += tk->i_blocksize > 0 ? ( i_size + tk->i_blocksize - 1 ) / tk->i_blocksize : 1;
             }
             toread[i_track].i_toread--;
         }
 
+        /* check new chunk and set new read pos */
         if( tk->i_idxposc < tk->idx.i_size)
         {
             toread[i_track].i_posf =
@@ -1449,7 +1457,7 @@ static int Demux_Seekable( demux_t *p_demux )
             }
 
         }
-        else
+        else /* all chunks read for this track */
         {
             toread[i_track].i_posf = -1;
         }
@@ -1566,7 +1574,7 @@ static int Demux_UnSeekable( demux_t *p_demux )
                         AVI_GetPTS( p_stream_master ) )< 2 * CLOCK_FREQ )
             {
                 /* load it and send to decoder */
-                block_t *p_frame = ReadFrame( p_demux, p_stream, 8, avi_pk.i_size + 8 ) ;
+                block_t *p_frame = ReadFrame( p_demux, p_stream, 8, avi_pk.i_size ) ;
                 if( p_frame == NULL )
                 {
                     return VLC_DEMUXER_EGENERIC;
@@ -1642,13 +1650,12 @@ static int Seek( demux_t *p_demux, vlc_tick_t i_date, int i_percent, bool b_accu
         if( !p_sys->i_length )
         {
             avi_track_t *p_stream = NULL;
-            unsigned i_stream = 0;
             uint64_t i_pos;
 
             if ( !p_sys->i_movi_lastchunk_pos && /* set when index is successfully loaded */
                  ! ( p_sys->i_avih_flags & AVIF_ISINTERLEAVED ) )
             {
-                msg_Err( p_demux, "seeking without index at %d%%"
+                msg_Warn( p_demux, "seeking without index at %d%%"
                          " only works for interleaved files", i_percent );
                 goto failandresetpos;
             }
@@ -1671,7 +1678,6 @@ static int Seek( demux_t *p_demux, vlc_tick_t i_date, int i_percent, bool b_accu
                     continue;
 
                 p_stream = p_track;
-                i_stream = i;
                 if( !p_track->b_eof )
                     break;
             }
@@ -1682,7 +1688,7 @@ static int Seek( demux_t *p_demux, vlc_tick_t i_date, int i_percent, bool b_accu
             }
 
             /* be sure that the index exist */
-            if( AVI_StreamChunkSet( p_demux, i_stream, 0 ) )
+            if( AVI_StreamChunkSet( p_demux, p_stream, 0 ) )
             {
                 msg_Warn( p_demux, "cannot seek" );
                 goto failandresetpos;
@@ -1693,7 +1699,7 @@ static int Seek( demux_t *p_demux, vlc_tick_t i_date, int i_percent, bool b_accu
             {
                 /* search after i_idxposc */
                 if( AVI_StreamChunkSet( p_demux,
-                                        i_stream, p_stream->i_idxposc + 1 ) )
+                                        p_stream, p_stream->i_idxposc + 1 ) )
                 {
                     msg_Warn( p_demux, "cannot seek" );
                     goto failandresetpos;
@@ -1965,7 +1971,7 @@ static vlc_tick_t AVI_GetPTS( avi_track_t *tk )
         return AVI_GetDPTS( tk, tk->i_idxposc );
 }
 
-static int AVI_StreamChunkFind( demux_t *p_demux, unsigned int i_stream )
+static int AVI_StreamChunkFind( demux_t *p_demux, avi_track_t *tk )
 {
     demux_sys_t *p_sys = p_demux->p_sys;
     avi_packet_t avi_pk;
@@ -2025,7 +2031,7 @@ static int AVI_StreamChunkFind( demux_t *p_demux, unsigned int i_stream )
             index.i_lengthtotal = index.i_length;
             avi_index_Append( &tk_pk->idx, &p_sys->i_movi_lastchunk_pos, &index );
 
-            if( avi_pk.i_stream == i_stream  )
+            if( tk_pk == tk )
             {
                 return VLC_SUCCESS;
             }
@@ -2039,12 +2045,9 @@ static int AVI_StreamChunkFind( demux_t *p_demux, unsigned int i_stream )
 }
 
 /* be sure that i_ck will be a valid index entry */
-static int AVI_StreamChunkSet( demux_t *p_demux, unsigned int i_stream,
+static int AVI_StreamChunkSet( demux_t *p_demux, avi_track_t *p_stream,
                                unsigned int i_ck )
 {
-    demux_sys_t *p_sys = p_demux->p_sys;
-    avi_track_t *p_stream = p_sys->track[i_stream];
-
     p_stream->i_idxposc = i_ck;
     p_stream->i_idxposb = 0;
 
@@ -2054,7 +2057,7 @@ static int AVI_StreamChunkSet( demux_t *p_demux, unsigned int i_stream,
         do
         {
             p_stream->i_idxposc++;
-            if( AVI_StreamChunkFind( p_demux, i_stream ) )
+            if( AVI_StreamChunkFind( p_demux, p_stream ) )
             {
                 return VLC_EGENERIC;
             }
@@ -2067,12 +2070,9 @@ static int AVI_StreamChunkSet( demux_t *p_demux, unsigned int i_stream,
 
 /* XXX FIXME up to now, we assume that all chunk are one after one */
 static int AVI_StreamBytesSet( demux_t    *p_demux,
-                               unsigned int i_stream,
+                               avi_track_t *p_stream,
                                uint64_t  i_byte )
 {
-    demux_sys_t *p_sys = p_demux->p_sys;
-    avi_track_t *p_stream = p_sys->track[i_stream];
-
     if( ( p_stream->idx.i_size > 0 )
         &&( i_byte < p_stream->idx.p_entry[p_stream->idx.i_size - 1].i_lengthtotal +
                 p_stream->idx.p_entry[p_stream->idx.i_size - 1].i_length ) )
@@ -2115,7 +2115,7 @@ static int AVI_StreamBytesSet( demux_t    *p_demux,
         do
         {
             p_stream->i_idxposc++;
-            if( AVI_StreamChunkFind( p_demux, i_stream ) )
+            if( AVI_StreamChunkFind( p_demux, p_stream ) )
             {
                 return VLC_EGENERIC;
             }
@@ -2135,35 +2135,28 @@ static int AVI_TrackSeek( demux_t *p_demux,
 {
     demux_sys_t  *p_sys = p_demux->p_sys;
     avi_track_t  *tk = p_sys->track[i_stream];
-
-#define p_stream    p_sys->track[i_stream]
     vlc_tick_t i_oldpts;
 
-    i_oldpts = AVI_GetPTS( p_stream );
+    i_oldpts = AVI_GetPTS( tk );
 
-    if( !p_stream->i_samplesize )
+    if( !tk->i_samplesize )
     {
-        if( AVI_StreamChunkSet( p_demux,
-                                i_stream,
-                                AVI_PTSToChunk( p_stream, i_date ) ) )
+        if( AVI_StreamChunkSet( p_demux, tk, AVI_PTSToChunk( tk, i_date ) ) )
         {
             return VLC_EGENERIC;
         }
 
-        if( p_stream->fmt.i_cat == AUDIO_ES )
+        if( tk->fmt.i_cat == AUDIO_ES )
         {
-            unsigned int i;
-            tk->i_blockno = 0;
-            for( i = 0; i < tk->i_idxposc; i++ )
+            if( tk->i_blocksize > 0 )
             {
-                if( tk->i_blocksize > 0 )
-                {
+                tk->i_blockno = tk->i_idxposc;
+            }
+            else
+            {
+                tk->i_blockno = 0;
+                for( unsigned int i = 0; i < tk->i_idxposc; i++ )
                     tk->i_blockno += ( tk->idx.p_entry[i].i_length + tk->i_blocksize - 1 ) / tk->i_blocksize;
-                }
-                else
-                {
-                    tk->i_blockno++;
-                }
             }
         }
 
@@ -2173,18 +2166,15 @@ static int AVI_TrackSeek( demux_t *p_demux,
                  i_oldpts > i_date ? ">" : "<",
                  i_date );
 
-        if( p_stream->fmt.i_cat == VIDEO_ES )
+        if( tk->fmt.i_cat == VIDEO_ES )
         {
             /* search key frame */
             //if( i_date < i_oldpts || 1 )
             {
-                while( p_stream->i_idxposc > 0 &&
-                   !( p_stream->idx.p_entry[p_stream->i_idxposc].i_flags &
-                                                                AVIIF_KEYFRAME ) )
+                while( tk->i_idxposc > 0 &&
+                   !( tk->idx.p_entry[tk->i_idxposc].i_flags & AVIIF_KEYFRAME ) )
                 {
-                    if( AVI_StreamChunkSet( p_demux,
-                                            i_stream,
-                                            p_stream->i_idxposc - 1 ) )
+                    if( AVI_StreamChunkSet( p_demux, tk, tk->i_idxposc - 1 ) )
                     {
                         return VLC_EGENERIC;
                     }
@@ -2193,13 +2183,10 @@ static int AVI_TrackSeek( demux_t *p_demux,
 #if 0
             else
             {
-                while( p_stream->i_idxposc < p_stream->idx.i_size &&
-                        !( p_stream->idx.p_entry[p_stream->i_idxposc].i_flags &
-                                                                AVIIF_KEYFRAME ) )
+                while( tk->i_idxposc < tk->idx.i_size &&
+                        !( tk->idx.p_entry[tk->i_idxposc].i_flags & AVIIF_KEYFRAME ) )
                 {
-                    if( AVI_StreamChunkSet( p_demux,
-                                            i_stream,
-                                            p_stream->i_idxposc + 1 ) )
+                    if( AVI_StreamChunkSet( p_demux, tk, tk->i_idxposc + 1 ) )
                     {
                         return VLC_EGENERIC;
                     }
@@ -2210,15 +2197,12 @@ static int AVI_TrackSeek( demux_t *p_demux,
     }
     else
     {
-        if( AVI_StreamBytesSet( p_demux,
-                                i_stream,
-                                AVI_PTSToByte( p_stream, i_date ) ) )
+        if( AVI_StreamBytesSet( p_demux, tk, AVI_PTSToByte( tk, i_date ) ) )
         {
             return VLC_EGENERIC;
         }
     }
     return VLC_SUCCESS;
-#undef p_stream
 }
 
 /****************************************************************************
@@ -2445,21 +2429,32 @@ static void avi_index_Clean( avi_index_t *p_index )
 {
     free( p_index->p_entry );
 }
-static void avi_index_Append( avi_index_t *p_index, uint64_t *pi_last_pos,
-                              avi_entry_t *p_entry )
+#define MAX_INDEX_ENTRIES __MIN(SIZE_MAX/sizeof(avi_entry_t), UINT32_MAX)
+#define INDEX_EXTENT 16384
+static int64_t avi_index_Append( avi_index_t *p_index, uint64_t *pi_last_pos,
+                                 avi_entry_t *p_entry )
 {
     /* Update last chunk position */
     if( *pi_last_pos < p_entry->i_pos )
          *pi_last_pos = p_entry->i_pos;
 
+    if( p_index->i_size == MAX_INDEX_ENTRIES )
+        return -1;
+
     /* add the entry */
     if( p_index->i_size >= p_index->i_max )
     {
-        p_index->i_max += 16384;
+        if( MAX_INDEX_ENTRIES - INDEX_EXTENT > p_index->i_max )
+            p_index->i_max += INDEX_EXTENT;
+        else
+            p_index->i_max = MAX_INDEX_ENTRIES;
         p_index->p_entry = realloc_or_free( p_index->p_entry,
-                                            p_index->i_max * sizeof( *p_index->p_entry ) );
+                                            p_index->i_max * sizeof(avi_entry_t) );
         if( !p_index->p_entry )
-            return;
+        {
+            avi_index_Init( p_index );
+            return -1;
+        }
     }
     /* calculate cumulate length */
     if( p_index->i_size > 0 )
@@ -2474,6 +2469,7 @@ static void avi_index_Append( avi_index_t *p_index, uint64_t *pi_last_pos,
     }
 
     p_index->p_entry[p_index->i_size++] = *p_entry;
+    return p_index->i_size - 1;
 }
 
 static int AVI_IndexFind_idx1( demux_t *p_demux,
@@ -2654,7 +2650,6 @@ static void AVI_IndexLoad_indx( demux_t *p_demux,
         avi_chunk_list_t    *p_strl;
         avi_chunk_indx_t    *p_indx;
 
-#define p_stream  p_sys->track[i_stream]
         p_strl = AVI_ChunkFind( p_hdrl, AVIFOURCC_strl, i_stream, true );
         p_indx = AVI_ChunkFind( p_strl, AVIFOURCC_indx, 0, false );
 
@@ -2693,7 +2688,6 @@ static void AVI_IndexLoad_indx( demux_t *p_demux,
         {
             msg_Warn( p_demux, "unknown type index(0x%x)", p_indx->i_indextype );
         }
-#undef p_stream
     }
 }
 
@@ -2745,7 +2739,7 @@ static void AVI_IndexLoad( demux_t *p_demux )
             b_key = p_index->p_entry[j].i_flags & AVIIF_KEYFRAME;
         if( !b_key )
         {
-            msg_Err( p_demux, "no key frame set for track %u", i );
+            msg_Warn( p_demux, "no key frame set for track %u", i );
             for( unsigned j = 0; j < p_index->i_size; j++ )
                 p_index->p_entry[j].i_flags |= AVIIF_KEYFRAME;
         }



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/7533c9eee9f827ae2da8afb6249e07791d098102...f098271f03dfb54a6fd029efb87ed144eb74d7de

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/7533c9eee9f827ae2da8afb6249e07791d098102...f098271f03dfb54a6fd029efb87ed144eb74d7de
You're receiving this email because of your account on code.videolan.org.


VideoLAN code repository instance


More information about the vlc-commits mailing list