The files API enables users to use the File System abstraction of IPFS. There are two Files API, one at the top level, the original
add
,cat
,get
andls
, and another behind thefiles
, also known as MFS
Explore the Mutable File System through interactive coding challenges in our ProtoSchool tutorial.
- The Regular API
- The Mutable Files API
ipfs.files.chmod(path, mode, [options])
ipfs.files.cp(...from, to, [options])
ipfs.files.mkdir(path, [options])
ipfs.files.stat(path, [options])
ipfs.files.touch(path, [options])
ipfs.files.rm(path, [options])
ipfs.files.read(path, [options])
ipfs.files.write(path, content, [options])
ipfs.files.mv(...from, to, [options])
ipfs.files.flush(path, [options])
ipfs.files.ls(path, [options])
The regular, top-level API for add, cat, get and ls Files on IPFS
Import a file or data into IPFS.
Name | Type | Description |
---|---|---|
data | Object | Data to import (see below) |
data
may be:
FileContent
(see below for definition)FileObject
(see below for definition)
FileObject
is a plain JS object of the following form:
{
// The path you want the file to be accessible at from the root CID _after_ it has been added
path?: string
// The contents of the file (see below for definition)
content?: FileContent
// File mode to store the entry with (see https://en.wikipedia.org/wiki/File_system_permissions#Numeric_notation)
mode?: number | string
// The modification time of the entry (see below for definition)
mtime?: UnixTime
}
If no path
is specified, then the item will be added to the root level and will be given a name according to it's CID.
If no content
is passed, then the item is treated as an empty directory.
One of path
or content
must be passed.
Both mode
and mtime
are optional and will result in different CIDs for the same file if passed.
mode
will have a default value applied if not set, see UnixFS Metadata for further discussion.
FileContent
is one of the following types:
Uint8Array | Blob | String | Iterable<Uint8Array> | Iterable<number> | AsyncIterable<Uint8Array> | ReadableStream<Uint8Array>
UnixTime
is one of the following types:
Date | { secs: number, nsecs?: number } | number[]
As an object, secs
is the number of seconds since (positive) or before (negative) the Unix Epoch began and nsecs
is the number of nanoseconds since the last full second.
As an array of numbers, it must have two elements, as per the output of process.hrtime()
.
An optional object which may have the following keys:
Name | Type | Default | Description |
---|---|---|---|
chunker | String |
'size-262144' |
chunking algorithm used to build ipfs DAGs |
cidVersion | Number |
0 |
the CID version to use when storing the data |
hashAlg | String |
'sha2-256' |
multihash hashing algorithm to use |
onlyHash | boolean |
false |
If true, will not add blocks to the blockstore |
pin | boolean |
true |
pin this object when adding |
progress | function | undefined |
a function that will be called with the number of bytes added as a file is added to ipfs and the path of the file being added |
rawLeaves | boolean |
false |
if true, DAG leaves will contain raw file data and not be wrapped in a protobuf |
trickle | boolean |
false |
if true will use the trickle DAG format for DAG generation |
wrapWithDirectory | boolean |
false |
Adds a wrapping node around the content |
timeout | Number |
undefined |
A timeout in ms |
signal | AbortSignal | undefined |
Can be used to cancel any long running requests started as a result of this call |
Type | Description |
---|---|
Promise<UnixFSEntry> |
A object describing the added data |
Each yielded object is of the form:
{
path: '/tmp/myfile.txt',
cid: CID('QmHash'),
mode: Number, // implicit if not provided - 0644 for files, 0755 for directories
mtime?: { secs: Number, nsecs: Number },
size: 123
}
const file = {
path: '/tmp/myfile.txt',
content: 'ABC'
}
const result = await ipfs.add(content)
console.info(result)
/*
Prints:
{
"path": "tmp",
"cid": CID("QmWXdjNC362aPDtwHPUE9o2VMqPeNeCQuTBTv1NsKtwypg"),
"mode": 493,
"mtime": { secs: Number, nsecs: Number },
"size": 67
}
*/
Now ipfs.io/ipfs/Qm..pg/myfile.txt returns the "ABC" string.
Import multiple files and data into IPFS.
Name | Type | Description |
---|---|---|
source | FileStream<FileContent|FileObject> | Data to import (see below) |
FileStream
is a stream of FileContent or FileObject entries of the type:
Iterable<FileContent|FileObject> | AsyncIterable<FileContent|FileObject> | ReadableStream<FileContent|FileObject>
An optional object which may have the following keys:
Name | Type | Default | Description |
---|---|---|---|
chunker | string |
'size-262144' |
chunking algorithm used to build ipfs DAGs |
cidVersion | number |
0 |
the CID version to use when storing the data |
enableShardingExperiment | boolean |
false |
allows to create directories with an unlimited number of entries currently size of unixfs directories is limited by the maximum block size. Note that this is an experimental feature |
hashAlg | String |
'sha2-256' |
multihash hashing algorithm to use |
onlyHash | boolean |
false |
If true, will not add blocks to the blockstore |
pin | boolean |
true |
pin this object when adding |
progress | function | undefined |
a function that will be called with the number of bytes added as a file is added to ipfs and the path of the file being added |
rawLeaves | boolean |
false |
if true, DAG leaves will contain raw file data and not be wrapped in a protobuf |
shardSplitThreshold | Number |
1000 |
Directories with more than this number of files will be created as HAMT-sharded directories |
trickle | boolean |
false |
if true will use the trickle DAG format for DAG generation |
wrapWithDirectory | boolean |
false |
Adds a wrapping node around the content |
timeout | Number |
undefined |
A timeout in ms |
signal | AbortSignal | undefined |
Can be used to cancel any long running requests started as a result of this call |
Type | Description |
---|---|
AsyncIterable<UnixFSEntry> |
An async iterable that yields objects describing the added data |
Each yielded object is of the form:
{
path: '/tmp/myfile.txt',
cid: CID('QmHash'),
mode: Number, // implicit if not provided - 0644 for files, 0755 for directories
mtime?: { secs: Number, nsecs: Number },
size: 123
}
const files = [{
path: '/tmp/myfile.txt',
content: 'ABC'
}]
for await (const result of ipfs.addAll(files)) {
console.log(result)
}
/*
Prints out objects like:
{
"path": "tmp",
"cid": CID("QmWXdjNC362aPDtwHPUE9o2VMqPeNeCQuTBTv1NsKtwypg"),
"mode": 493,
"mtime": { secs: Number, nsecs: Number },
"size": 67
}
{
"path": "/tmp/myfile.txt",
"cid": CID("QmNz1UBzpdd4HfZ3qir3aPiRdX5a93XwTuDNyXRc6PKhWW"),
"mode": 420,
"mtime": { secs: Number, nsecs: Number },
"size": 11
}
*/
Now ipfs.io/ipfs/Qm...WW returns the "ABC" string.
The chunker
option can be one of the following formats:
- size-{size}
- rabin
- rabin-{avg}
- rabin-{min}-{avg}-{max}
size-*
will result in fixed-size chunks, rabin(-*)
will use rabin fingerprinting to potentially generate variable size chunks.
See the multihash module for the list of all possible values.
Both js-ipfs and js-ipfs-http-client export a utility to make importing files from the file system easier (Note: it not available in the browser).
const IPFS = require('ipfs')
const { globSource } = IPFS
const ipfs = await IPFS.create()
//options specific to globSource
const globSourceOptions = {
recursive: true
};
//example options to pass to IPFS
const addOptions = {
pin: true,
wrapWithDirectory: true,
timeout: 10000
};
for await (const file of ipfs.addAll(globSource('./docs', globSourceOptions), addOptions)) {
console.log(file)
}
/*
{
path: 'docs/assets/anchor.js',
cid: CID('QmVHxRocoWgUChLEvfEyDuuD6qJ4PhdDL2dTLcpUy3dSC2'),
size: 15347
}
{
path: 'docs/assets/bass-addons.css',
hash: CID('QmPiLWKd6yseMWDTgHegb8T7wVS7zWGYgyvfj7dGNt2viQ'),
size: 232
}
...
*/
Both js-ipfs and js-ipfs-http-client export a utility to make importing a file from a URL easier.
const IPFS = require('ipfs')
const { urlSource } = IPFS
const ipfs = await IPFS.create()
const file = await ipfs.add(urlSource('https://ipfs.io/images/ipfs-logo.svg'))
console.log(file)
/*
{
path: 'ipfs-logo.svg',
cid: CID('QmTqZhR6f7jzdhLgPArDPnsbZpvvgxzCZycXK7ywkLxSyU'),
size: 3243
}
*/
A great source of examples can be found in the tests for this API.
Returns a file addressed by a valid IPFS Path.
Name | Type | Description |
---|---|---|
ipfsPath | String or CID | An IPFS path or CID to export |
An optional object which may have the following keys:
Name | Type | Default | Description |
---|---|---|---|
offset | Number |
undefined |
An offset to start reading the file from |
length | Number |
undefined |
An optional max length to read from the file |
timeout | Number |
undefined |
A timeout in ms |
signal | AbortSignal | undefined |
Can be used to cancel any long running requests started as a result of this call |
Type | Description |
---|---|
AsyncIterable<Uint8Array> |
An async iterable that yields Uint8Array objects with the contents of path |
for await (const chunk of ipfs.cat(ipfsPath)) {
console.info(chunk)
}
A great source of examples can be found in the tests for this API.
Fetch a file or an entire directory tree from IPFS that is addressed by a valid IPFS Path.
Name | Type | Description |
---|---|---|
ipfsPath | String or CID | An IPFS path or CID to export |
An optional object which may have the following keys:
Name | Type | Default | Description |
---|---|---|---|
timeout | Number |
undefined |
A timeout in ms |
signal | AbortSignal | undefined |
Can be used to cancel any long running requests started as a result of this call |
Type | Description |
---|---|
AsyncIterable<Object> |
An async iterable that yields objects representing the files |
Each yielded object is of the form:
{
type: string, // 'file' or 'dir'
path: string, // a deeply nested path within the directory structure
content?: <AsyncIterable<Uint8Array>>, // only present if `type` is 'file'
mode: Number, // implicit if not provided - 0644 for files, 0755 for directories
mtime?: { secs: Number, nsecs: Number }
}
Here, each path
corresponds to the name of a file, and content
is an async iterable with the file contents.
const cid = 'QmQ2r6iMNpky5f1m4cnm3Yqw8VSvjuKpTcK1X7dBR1LkJF'
for await (const file of ipfs.get(cid)) {
console.log(file.type, file.path)
if (!file.content) continue;
const content = []
for await (const chunk of file.content) {
content.push(chunk)
}
console.log(content)
}
When invoking this method via the HTTP API client, the response arrives as a stream containing either the entire contents of the file (if the passed CID resolves to a file) or recursive directory tree and all files contained therein (if the passed CID resolves to a directory).
If you are iterating over a directory, in order to proceed to the next entry in the stream, you must consume the content
field of the current entry if it is present.
A great source of examples can be found in the tests for this API.
Lists a directory from IPFS that is addressed by a valid IPFS Path.
Name | Type | Description |
---|---|---|
ipfsPath | String or CID | An IPFS path or CID to list |
An optional object which may have the following keys:
Name | Type | Default | Description |
---|---|---|---|
timeout | Number |
undefined |
A timeout in ms |
signal | AbortSignal | undefined |
Can be used to cancel any long running requests started as a result of this call |
Type | Description |
---|---|
AsyncIterable<Object> |
An async iterable that yields objects representing the files |
Each yielded object is of the form:
{
depth: 1,
name: 'alice.txt',
path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/alice.txt',
size: 11696,
cid: CID('QmZyUEQVuRK3XV7L9Dk26pg6RVSgaYkiSTEdnT2kZZdwoi'),
type: 'file',
mode: Number, // implicit if not provided - 0644 for files, 0755 for directories
mtime?: { secs: Number, nsecs: Number }
}
const cid = 'QmQ2r6iMNpky5f1m4cnm3Yqw8VSvjuKpTcK1X7dBR1LkJF'
for await (const file of ipfs.ls(cid)) {
console.log(file.path)
}
A great source of examples can be found in the tests for this API.
The Mutable File System (MFS) is a virtual file system on top of IPFS that exposes a Unix like API over a virtual directory. It enables users to write and read from paths without having to worry about updating the graph. It enables things like ipfs-blob-store to exist.
Change mode for files and directories
Name | Type | Description |
---|---|---|
path | String or CID | An MFS Path, IPFS path or CID to modify |
mode | String or Number | An integer (e.g. 0o755 or parseInt('0755', 8) ) or a string modification of the existing mode, e.g. 'a+x' , 'g-w' , etc |
An optional object which may have the following keys:
Name | Type | Default | Description |
---|---|---|---|
recursive | boolean |
false |
If true mode will be applied to the entire tree under path |
flush | boolean |
true |
If true the changes will be immediately flushed to disk |
hashAlg | String |
'sha2-256' |
The hash algorithm to use for any updated entries |
cidVersion | Number |
0 |
The CID version to use for any updated entries |
timeout | Number |
undefined |
A timeout in ms |
signal | AbortSignal | undefined |
Can be used to cancel any long running requests started as a result of this call |
Type | Description |
---|---|
Promise<void> |
If action is successfully completed. Otherwise an error will be thrown |
// To give a file -rwxrwxrwx permissions
await ipfs.files.chmod('/path/to/file.txt', parseInt('0777', 8))
// Alternatively
await ipfs.files.chmod('/path/to/file.txt', '+rwx')
// You can omit the leading `0` too
await ipfs.files.chmod('/path/to/file.txt', '777')
Copy files from one location to another
Name | Type | Description |
---|---|---|
from | One or more Strings or CIDs | An MFS path, IPFS path or CID |
to | String |
An MFS path |
An optional object which may have the following keys:
Name | Type | Default | Description |
---|---|---|---|
parents | boolean |
false |
If true, create intermediate directories |
flush | boolean |
true |
If true the changes will be immediately flushed to disk |
hashAlg | String |
'sha2-256' |
The hash algorithm to use for any updated entries |
cidVersion | Number |
0 |
The CID version to use for any updated entries |
timeout | Number |
undefined |
A timeout in ms |
signal | AbortSignal | undefined |
Can be used to cancel any long running requests started as a result of this call |
Type | Description |
---|---|
Promise<void> |
If action is successfully completed. Otherwise an error will be thrown |
// To copy a file
await ipfs.files.cp('/src-file', '/dst-file')
// To copy a directory
await ipfs.files.cp('/src-dir', '/dst-dir')
// To copy multiple files to a directory
await ipfs.files.cp(['/src-file1', '/src-file2'], '/dst-dir')
If from
has multiple values then to
must be a directory.
If from
has a single value and to
exists and is a directory, from
will be copied into to
.
If from
has a single value and to
exists and is a file, from
must be a file and the contents of to
will be replaced with the contents of from
otherwise an error will be returned.
If from
is an IPFS path, and an MFS path exists with the same name, the IPFS path will be chosen.
If from
is an IPFS path and the content does not exist in your node's repo, only the root node of the source file with be retrieved from the network and linked to from the destination. The remainder of the file will be retrieved on demand.
Make a directory in your MFS
Name | Type | Description |
---|---|---|
path | String |
The MFS path to create a directory at |
An optional object which may have the following keys:
Name | Type | Default | Description |
---|---|---|---|
parents | boolean |
false |
If true, create intermediate directories |
mode | Number |
undefined |
An integer that represents the file mode |
mtime | Object |
undefined |
A Date object, an object with { secs, nsecs } properties where secs is the number of seconds since (positive) or before (negative) the Unix Epoch began and nsecs is the number of nanoseconds since the last full second, or the output of process.hrtime() |
flush | boolean |
true |
If true the changes will be immediately flushed to disk |
hashAlg | String |
'sha2-256' |
The hash algorithm to use for any updated entries |
cidVersion | Number |
0 |
The CID version to use for any updated entries |
timeout | Number |
undefined |
A timeout in ms |
signal | AbortSignal | undefined |
Can be used to cancel any long running requests started as a result of this call |
Type | Description |
---|---|
Promise<void> |
If action is successfully completed. Otherwise an error will be thrown |
await ipfs.files.mkdir('/my/beautiful/directory')
Get file or directory statistics
Name | Type | Description |
---|---|---|
path | String |
The MFS path return statistics from |
An optional object which may have the following keys:
Name | Type | Default | Description |
---|---|---|---|
hash | boolean |
false |
If true, return only the CID |
size | boolean |
false |
If true, return only the size |
withLocal | boolean |
false |
If true, compute the amount of the DAG that is local and if possible the total size |
timeout | Number |
undefined |
A timeout in ms |
signal | AbortSignal | undefined |
Can be used to cancel any long running requests started as a result of this call |
Type | Description |
---|---|
Promise<Object> |
An object containing the file/directory status |
the returned object has the following keys:
cid
a CID instancesize
is an integer with the file size in BytescumulativeSize
is an integer with the size of the DAGNodes making up the file in Bytestype
is a string that can be eitherdirectory
orfile
blocks
iftype
isdirectory
, this is the number of files in the directory. If it isfile
it is the number of blocks that make up the filewithLocality
is a boolean to indicate if locality information is presentlocal
is a boolean to indicate if the queried dag is fully present locallysizeLocal
is an integer indicating the cumulative size of the data present locally
const stats = await ipfs.files.stat('/file.txt')
console.log(stats)
// {
// hash: CID('QmXmJBmnYqXVuicUfn9uDCC8kxCEEzQpsAbeq1iJvLAmVs'),
// size: 60,
// cumulativeSize: 118,
// blocks: 1,
// type: 'file'
// }
Update the mtime of a file or directory
Name | Type | Description |
---|---|---|
path | String |
The MFS path to update the mtime for |
An optional object which may have the following keys:
Name | Type | Default | Description |
---|---|---|---|
mtime | Object |
Now | Either a Date object, an object with { sec, nsecs } properties or the output of process.hrtime() |
flush | boolean |
true |
If true the changes will be immediately flushed to disk |
hashAlg | String |
'sha2-256' |
The hash algorithm to use for any updated entries |
cidVersion | Number |
0 |
The CID version to use for any updated entries |
timeout | Number |
undefined |
A timeout in ms |
signal | AbortSignal | undefined |
Can be used to cancel any long running requests started as a result of this call |
Type | Description |
---|---|
Promise<void> |
If action is successfully completed. Otherwise an error will be thrown |
// set the mtime to the current time
await ipfs.files.touch('/path/to/file.txt')
// set the mtime to a specific time
await ipfs.files.touch('/path/to/file.txt', {
mtime: new Date('May 23, 2014 14:45:14 -0700')
})
Remove a file or directory.
Name | Type | Description |
---|---|---|
path | String or Array<String> |
One or more MFS paths to remove |
An optional object which may have the following keys:
Name | Type | Default | Description |
---|---|---|---|
recursive | boolean |
false |
If true all paths under the specifed path(s) will be removed |
flush | boolean |
true |
If true the changes will be immediately flushed to disk |
hashAlg | String |
'sha2-256' |
The hash algorithm to use for any updated entries |
cidVersion | Number |
0 |
The CID version to use for any updated entries |
timeout | Number |
undefined |
A timeout in ms |
signal | AbortSignal | undefined |
Can be used to cancel any long running requests started as a result of this call |
Type | Description |
---|---|
Promise<void> |
If action is successfully completed. Otherwise an error will be thrown |
// To remove a file
await ipfs.files.rm('/my/beautiful/file.txt')
// To remove multiple files
await ipfs.files.rm(['/my/beautiful/file.txt', '/my/other/file.txt'])
// To remove a directory
await ipfs.files.rm('/my/beautiful/directory', { recursive: true })
Read a file
Name | Type | Description |
---|---|---|
path | String or CID |
An MFS path, IPFS Path or CID to read |
An optional object which may have the following keys:
Name | Type | Default | Description |
---|---|---|---|
offset | Number |
undefined |
An offset to start reading the file from |
length | Number |
undefined |
An optional max length to read from the file |
timeout | Number |
undefined |
A timeout in ms |
signal | AbortSignal | undefined |
Can be used to cancel any long running requests started as a result of this call |
Type | Description |
---|---|
AsyncIterable<Uint8Array> |
An async iterable that yields Uint8Array objects with the contents of path |
const chunks = []
for await (const chunk of ipfs.files.read('/hello-world')) {
chunks.push(chunk)
}
console.log(uint8ArrayConcat(chunks).toString())
// Hello, World!
Write to an MFS path
Name | Type | Description |
---|---|---|
path | String |
The MFS path where you will write to |
content | String , Uint8Array , AsyncIterable<Uint8Array> or Blob |
The content to write to the path |
An optional object which may have the following keys:
Name | Type | Default | Description |
---|---|---|---|
offset | Number |
undefined |
An offset to start writing to file at |
length | Number |
undefined |
Optionally limit how many bytes are read from the stream |
create | boolean |
false |
Create the MFS path if it does not exist |
parents | boolean |
false |
Create intermediate MFS paths if they do not exist |
truncate | boolean |
false |
Truncate the file at the MFS path if it would have been larger than the passed content |
rawLeaves | boolean |
false |
If true, DAG leaves will contain raw file data and not be wrapped in a protobuf |
mode | Number |
undefined |
An integer that represents the file mode |
mtime | Object |
undefined |
A Date object, an object with { secs, nsecs } properties where secs is the number of seconds since (positive) or before (negative) the Unix Epoch began and nsecs is the number of nanoseconds since the last full second, or the output of process.hrtime() |
flush | boolean |
true |
If true the changes will be immediately flushed to disk |
hashAlg | String |
'sha2-256' |
The hash algorithm to use for any updated entries |
cidVersion | Number |
0 |
The CID version to use for any updated entries |
timeout | Number |
undefined |
A timeout in ms |
signal | AbortSignal | undefined |
Can be used to cancel any long running requests started as a result of this call |
Type | Description |
---|---|
Promise<void> |
If action is successfully completed. Otherwise an error will be thrown |
await ipfs.files.write('/hello-world', new TextEncoder().encode('Hello, world!'))
Move files from one location to another
Name | Type | Description |
---|---|---|
...from | String |
One or more MFS paths to move |
to | String |
The location to move files to |
An optional object which may have the following keys:
Name | Type | Default | Description |
---|---|---|---|
parents | boolean |
false |
Create intermediate MFS paths if they do not exist |
flush | boolean |
true |
If true the changes will be immediately flushed to disk |
hashAlg | String |
'sha2-256' |
The hash algorithm to use for any updated entries |
cidVersion | Number |
0 |
The CID version to use for any updated entries |
timeout | Number |
undefined |
A timeout in ms |
signal | AbortSignal | undefined |
Can be used to cancel any long running requests started as a result of this call |
Type | Description |
---|---|
Promise<void> |
If action is successfully completed. Otherwise an error will be thrown |
await ipfs.files.mv('/src-file', '/dst-file')
await ipfs.files.mv('/src-dir', '/dst-dir')
await ipfs.files.mv(['/src-file1', '/src-file2'], '/dst-dir')
If from
has multiple values then to
must be a directory.
If from
has a single value and to
exists and is a directory, from
will be moved into to
.
If from
has a single value and to
exists and is a file, from
must be a file and the contents of to
will be replaced with the contents of from
otherwise an error will be returned.
If from
is an IPFS path, and an MFS path exists with the same name, the IPFS path will be chosen.
If from
is an IPFS path and the content does not exist in your node's repo, only the root node of the source file with be retrieved from the network and linked to from the destination. The remainder of the file will be retrieved on demand.
All values of from
will be removed after the operation is complete unless they are an IPFS path.
Flush a given path's data to the disk
Name | Type | Description |
---|---|---|
path | String |
The MFS path to flush |
An optional object which may have the following keys:
Name | Type | Default | Description |
---|---|---|---|
timeout | Number |
undefined |
A timeout in ms |
signal | AbortSignal | undefined |
Can be used to cancel any long running requests started as a result of this call |
Type | Description |
---|---|
Promise<CID> |
The CID of the path that has been flushed |
const cid = await ipfs.files.flush('/')
List directories in the local mutable namespace
Name | Type | Description |
---|---|---|
path | String |
The MFS path to list |
An optional object which may have the following keys:
Name | Type | Default | Description |
---|---|---|---|
timeout | Number |
undefined |
A timeout in ms |
signal | AbortSignal | undefined |
Can be used to cancel any long running requests started as a result of this call |
Type | Description |
---|---|
AsyncIterable<Object> |
An async iterable that yields objects representing the files |
Each object contains the following keys:
name
which is the file's nametype
which is the object's type (directory
orfile
)size
the size of the file in bytescid
the hash of the file (A CID instance)mode
the UnixFS mode as a Numbermtime
an objects with numericsecs
andnsecs
properties
for await (const file of ipfs.files.ls('/screenshots')) {
console.log(file.name)
}
// 2018-01-22T18:08:46.775Z.png
// 2018-01-22T18:08:49.184Z.png