Lấy loại Mime của tệp trong Java


336

Tôi chỉ tự hỏi làm thế nào hầu hết mọi người lấy một loại mime từ một tệp trong Java? Cho đến nay tôi đã thử hai dụng cụ: JMimeMagic&Mime-Util .

Cái đầu tiên cho tôi ngoại lệ bộ nhớ, cái thứ hai không đóng luồng của nó đúng cách. Tôi chỉ tự hỏi liệu có ai khác có một phương pháp / thư viện mà họ đã sử dụng và làm việc chính xác không?


4
Một tổng quan tốt về các thư viện có sẵn được đưa ra tại rgagnon.com/javadetails/java-0487.html
koppor

Tôi đã sử dụng lớp được đăng dưới dạng câu trả lời ở đây: stackoverflow.com/a/10140531/293280
Joshua Pinter

3
Tika nên là câu trả lời ngay bây giờ. Các câu trả lời khác dưới đây làm sáng tỏ nhiều sự phụ thuộc với Tika, nhưng tôi không thấy câu trả lời nào với tika-core.
javamonkey79

@ javamonkey79 khi chúng tôi sử dụng TIka, nó bao trùm tệp và nó không còn sử dụng được nữa. Chuỗi contentType = tika.detect (is).
Cool Techie

Câu trả lời:


326

Trong Java 7 bây giờ bạn có thể chỉ cần sử dụng Files.probeContentType(path).


62
Xin lưu ý rằng Files.probeContentType (Đường dẫn) có lỗi trên một số HĐH và rất nhiều báo cáo lỗi đã được nộp. Tôi đã gặp sự cố với phần mềm hoạt động trên Ubuntu nhưng bị lỗi trên windows. Dường như trên windows Files.probeContentType (Path) luôn trả về null. Đó không phải là hệ thống của tôi nên tôi đã không kiểm tra phiên bản JRE hoặc windows. Đó là windows 7 hoặc 8 có lẽ với orest JRE cho java 7.
Silver

13
Tôi đang chạy trên OS X 10.9 và tôi nhận nullra .xml, .png.xhtmltập tin. Tôi không biết nếu tôi chỉ làm điều gì đó sai lầm khủng khiếp, nhưng điều đó có vẻ khá khủng khiếp.

36
Một hạn chế lớn với điều này là tệp phải tồn tại trên hệ thống tệp. Điều này không hoạt động với một luồng hoặc một mảng byte, v.v.
Necreaux

3
phương pháp này không thể trở về kịch câm gõ khi tôi loại bỏ phần mở rộng từ exmaple name.For nếu tên là test.mp4 tôi thay đổi nó thành "test" và phương pháp lợi nhuận null.Also mở rộng thay đổi bộ phim tôi để png vv nó trả png loại mime
Sarkhan

10
Điều này là vô ích nếu tệp có phần mở rộng bị thiếu hoặc sai.
shmosel

215

Không may,

mimeType = file.toURL().openConnection().getContentType();

không hoạt động, vì việc sử dụng URL này khiến tệp bị khóa, do đó, chẳng hạn, nó không thể xóa được.

Tuy nhiên, bạn có điều này:

mimeType= URLConnection.guessContentTypeFromName(file.getName());

và cũng như sau, có lợi thế vượt ra ngoài việc sử dụng phần mở rộng tập tin và xem qua nội dung

InputStream is = new BufferedInputStream(new FileInputStream(file));
mimeType = URLConnection.guessContentTypeFromStream(is);
 //...close stream

Tuy nhiên, như được đề xuất bởi nhận xét ở trên, bảng tích hợp các loại mime khá hạn chế, không bao gồm, ví dụ, MSWord và PDF. Vì vậy, nếu bạn muốn khái quát hóa, bạn sẽ cần vượt ra ngoài các thư viện tích hợp, sử dụng, ví dụ: Mime-Util (là một thư viện tuyệt vời, sử dụng cả phần mở rộng tệp và nội dung).


8
Giải pháp hoàn hảo - đã giúp tôi rất nhiều! Gói FileInputStreamvào BufferedInputStreamlà một phần rất quan trọng - nếu không thì guessContentTypeFromStreamtrả lại null(thông qua InputStreamnên hỗ trợ nhãn hiệu)
Yuriy Nakonechnyy

11
Howerver, URLConnectioncó một bộ các loại nội dung rất hạn chế mà nó nhận ra. Ví dụ, nó không thể phát hiện application/pdf.
kpentchev

3
Nó chỉ khiến nó bị khóa bởi vì bạn không còn cách nào để đóng nó. Ngắt kết nối URLConnection sẽ mở khóa nó.
Hầu tước Lorne

1
cả đoánContentTypeFromStream cũng không đoánContentTypeFromName KHÔNG nhận ra ví dụ: mp4
Hartmut P.

3
guessContentTypeFromName()sử dụng $JAVA_HOME/lib/content-types.propertiestập tin mặc định . bạn có thể thêm tệp mở rộng của riêng mình bằng cách thay đổi thuộc tính hệ thốngSystem.setProperty("content.types.user.table","/lib/path/to/your/property/file");
Rasika Perera

50

API JAF là một phần của JDK 6. Nhìn vào javax.activationgói.

Hầu hết các lớp thú vị là javax.activation.MimeType- một người giữ loại MIME thực tế - và javax.activation.MimetypesFileTypeMap- lớp có thể hiện giải quyết loại MIME dưới dạng Chuỗi cho một tệp:

String fileName = "/path/to/file";
MimetypesFileTypeMap mimeTypesMap = new MimetypesFileTypeMap();

// only by file name
String mimeType = mimeTypesMap.getContentType(fileName);

// or by actual File instance
File file = new File(fileName);
mimeType = mimeTypesMap.getContentType(file);

4
Thật không may, như javadoc cho getContentType(File)các trạng thái: Trả về loại MIME của đối tượng tệp. Việc thực hiện trong các cuộc gọi lớp này getContentType(f.getName()).
Matyas

3
Và hãy nhớ rằng bạn có thể mở rộng chức năng này với tệp META-INF / mime.types để nó hoàn hảo nếu bạn buộc phải sử dụng Java 6. docs.oracle.com/javaee/5/api/javax/activation/,
Chrecir

7
bạn có thể bỏ qua việc tạo một đối tượng mới bằng cáchMimetypesFileTypeMap.getDefaultFileTypeMap().getContentType(file)
akostadinov 23/2/2015

Cảm ơn câu trả lời của bạn. Nó làm việc thành công cho tôi.
Radadiya Nikunj

Nhưng nó vẫn trả về loại nội dung chỉ dựa trên tên tệp. Và điều này đặc biệt nguy hiểm đối với các tệp được tải lên bởi người dùng.
Serge Ponomarev

47

Với Apache Tika, bạn chỉ cần ba dòng mã :

File file = new File("/path/to/file");
Tika tika = new Tika();
System.out.println(tika.detect(file));

Nếu bạn có một bảng điều khiển Groovy, chỉ cần dán và chạy mã này để chơi với nó:

@Grab('org.apache.tika:tika-core:1.14')
import org.apache.tika.Tika;

def tika = new Tika()
def file = new File("/path/to/file")
println tika.detect(file)

Hãy nhớ rằng API của nó rất phong phú, nó có thể phân tích "bất cứ thứ gì". Kể từ tika-core 1.14, bạn có:

String  detect(byte[] prefix)
String  detect(byte[] prefix, String name)
String  detect(File file)
String  detect(InputStream stream)
String  detect(InputStream stream, Metadata metadata)
String  detect(InputStream stream, String name)
String  detect(Path path)
String  detect(String name)
String  detect(URL url)

Xem các apidocs để biết thêm thông tin.


1
Nó không hoạt động cho csv. wtf? stackoverflow.com/questions/46960231/
Mạnh

1
Một điều tồi tệ về Tika, rất nhiều sự phụ thuộc phình to. Nó tăng kích thước bình của tôi lên 54MB !!!
helmy

1
@helmyTika 1.17 là độc lập và chỉ lớn 648 KB.
Hải Nam

... hoặc chỉ new Tika().detect(file.toPath())để phát hiện dựa trên tiện ích mở rộng của tệp chứ không phải phát hiện dựa trên nội dung của tệp
Lu55

Tài liệu @ Lu55 nói rằng vẫn sử dụng nội dung tài liệu. Tôi nghĩ bạn có nghĩa là new Tika().detect(file.getPath()), chỉ sử dụng phần mở rộng tập tin
delucasvb

31

Apache Tika cung cấp cho tika-core một phát hiện loại mime dựa trên các dấu hiệu ma thuật trong tiền tố luồng. tika-corekhông tìm nạp các phụ thuộc khác, điều này làm cho nó nhẹ như Tiện ích phát hiện loại Mime hiện không rõ ràng .

Ví dụ mã đơn giản (Java 7), sử dụng các biến theInputStreamtheFileName

try (InputStream is = theInputStream;
        BufferedInputStream bis = new BufferedInputStream(is);) {
    AutoDetectParser parser = new AutoDetectParser();
    Detector detector = parser.getDetector();
    Metadata md = new Metadata();
    md.add(Metadata.RESOURCE_NAME_KEY, theFileName);
    MediaType mediaType = detector.detect(bis, md);
    return mediaType.toString();
}

Xin lưu ý rằng MediaType.detect (...) không thể được sử dụng trực tiếp ( TIKA-1120 ). Nhiều gợi ý hơn được cung cấp tại https://tika.apache.org/0.10/detection.html .


1
+1 Cũng Metadata.RESOURCE_NAME_KEYcó thể được bỏ qua (nếu bạn không có hoặc không thể dựa vào tên gốc), nhưng trong trường hợp đó, bạn sẽ nhận được kết quả sai trong một số trường hợp (ví dụ: tài liệu văn phòng).
dùng1516873

Nó có một số vấn đề phát hiện XLSX nếu không có phần mở rộng về tên tệp ... nhưng giải pháp này đơn giản và thanh lịch.
Oscar Pérez

23

Nếu bạn là nhà phát triển Android, bạn có thể sử dụng lớp tiện ích android.webkit.MimeTypeMap ánh xạ các loại MIME sang phần mở rộng tệp và ngược lại.

Đoạn mã sau có thể giúp bạn.

private static String getMimeType(String fileUrl) {
    String extension = MimeTypeMap.getFileExtensionFromUrl(fileUrl);
    return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
}

3
Điều này cũng hoạt động nếu được thử với các đường dẫn tệp cục bộ, chẳng hạn như "/sdcard/path/to/video.extension". Vấn đề là nếu tệp cục bộ chứa không gian trong đường dẫn của nó, nó luôn trả về null
nmxprime

17

Từ hoa hồng :

FileNameMap fileNameMap = URLConnection.getFileNameMap();
String mimeType = fileNameMap.getContentTypeFor("alert.gif");

7
Bất cứ ai bình chọn xuống câu trả lời, xin vui lòng thêm một nhận xét để tôi (và những người khác) có thể học cách đăng câu trả lời tốt hơn.
AlikElzin-kilaka

3
Tôi đã không bỏ phiếu cho bạn nhưng, getFileNameMap không hoạt động đối với nhiều loại tệp cơ bản, ví dụ 'bmp'. Ngoài ra URLConnection.guessContentTypeFromName cũng trả lại điều tương tự
Ovidiu Buligan

5
Chức năng rất không đầy đủ. Kể từ các phần mở rộng Java 7, html, pdf và jpeg trả về đúng loại mime nhưng js và css trả về null!
djsumdog

Tôi đã thử nghiệm với 'webm' và nó trả về null.
Henrique Rocha

16

Nếu bạn bị mắc kẹt với java 5-6 thì lớp tiện ích này từ sản phẩm mã nguồn mở servoy .

Bạn chỉ cần chức năng này

public static String getContentType(byte[] data, String name)

Nó thăm dò các byte đầu tiên của nội dung và trả về các loại nội dung dựa trên nội dung đó chứ không phải bởi phần mở rộng tệp.


Làm việc cho các loại tệp đơn giản, phổ biến và ít loại tôi cần :)
user489041

13

Tôi chỉ tự hỏi làm thế nào hầu hết mọi người lấy một loại mime từ một tệp trong Java?

Tôi đã xuất bản SimpleMagic của tôi gói Java cho phép xác định kiểu nội dung (kiểu mime) từ các tệp và mảng byte. Nó được thiết kế để đọc và chạy tệp Unix (1) tệp ma thuật lệnh là một phần của hầu hết các cấu hình hệ điều hành Unix.

Tôi đã thử Apache Tika nhưng nó rất lớn với hàng tấn phụ thuộc, URLConnectionkhông sử dụng byte của các tệp vàMimetypesFileTypeMap cũng chỉ nhìn vào tên tệp.

Với SimpleMagic bạn có thể làm một cái gì đó như:

// create a magic utility using the internal magic file
ContentInfoUtil util = new ContentInfoUtil();
// if you want to use a different config file(s), you can load them by hand:
// ContentInfoUtil util = new ContentInfoUtil("/etc/magic");
...
ContentInfo info = util.findMatch("/tmp/upload.tmp");
// or
ContentInfo info = util.findMatch(inputStream);
// or
ContentInfo info = util.findMatch(contentByteArray);

// null if no match
if (info != null) {
   String mimeType = info.getMimeType();
}

1
Đã thử nghiệm nó trên nhiều tập tin hình ảnh. Tất cả đã được đổi tên. Thư viện tuyệt vời của bạn xử lý nó đúng cách. Tất nhiên là ánh sáng của nó quá :).
saurabheights

1
Vâng, điều này hoạt động tốt. Và đối với những người cần sử dụng giải pháp này trong Android, bạn chỉ cần bao gồm các phần sau trong tệp build.gradle: compile ('com.j256.simplemagic: Simplemagic: 1.10')
jkincali

1
Đây là một giải pháp tuyệt vời! Cảm ơn!
javydreamercsw

5

Để gắn chip với 5 xu của tôi:

TL, DR

Tôi sử dụng MimetypesFileTypeMap và thêm bất kỳ mime nào không có ở đó và tôi đặc biệt cần nó, vào tệp mime.types.

Và bây giờ, đọc dài:

Trước hết, danh sách các loại MIME rất lớn , xem tại đây: https://www.iana.org/assignments/media-types/media-types.xhtml

Tôi thích sử dụng các tiện ích tiêu chuẩn do JDK cung cấp trước tiên và nếu điều đó không hiệu quả, tôi sẽ đi tìm thứ khác.

Xác định loại tệp từ phần mở rộng tệp

Kể từ 1.6, Java có MimetypesFileTypeMap, như được chỉ ra trong một trong những câu trả lời ở trên và đó là cách đơn giản nhất để xác định loại mime:

new MimetypesFileTypeMap().getContentType( fileName );

Trong triển khai vanilla của nó, điều này không làm được gì nhiều (nghĩa là nó hoạt động với .html nhưng nó không cho .png). Tuy nhiên, rất đơn giản để thêm bất kỳ loại nội dung nào bạn có thể cần:

  1. Tạo tệp có tên 'mime.types' trong thư mục META-INF trong dự án của bạn
  2. Thêm một dòng cho mọi loại mime bạn cần và việc triển khai mặc định không cung cấp (có hàng trăm loại mime và danh sách tăng lên khi thời gian trôi qua).

Các mục ví dụ cho các tệp png và js sẽ là:

image/png png PNG
application/javascript js

Đối với định dạng tệp mime.types, xem thêm chi tiết tại đây: https://docs.oracle.com/javase/7/docs/api/javax/activation/MimetypesFileTypeMap.html

Xác định loại tệp từ nội dung tệp

Kể từ 1.7, Java có java.nio.file.spi.FileTypeDetector , định nghĩa API tiêu chuẩn để xác định loại tệp theo cách cụ thể thực hiện .

Để tìm nạp loại mime cho một tệp, bạn chỉ cần sử dụng Tệp và thực hiện điều này trong mã của mình:

Files.probeContentType(Paths.get("either file name or full path goes here"));

Định nghĩa API cung cấp cho các phương tiện hỗ trợ xác định loại mime tệp từ tên tệp hoặc từ nội dung tệp (byte ma thuật). Đó là lý do tại sao thăm dòContentType () phương thức ném IOException, trong trường hợp việc triển khai API này sử dụng Đường dẫn được cung cấp cho nó để thực sự cố gắng mở tệp được liên kết với nó.

Một lần nữa, vanilla thực hiện điều này (cái đi kèm với JDK) để lại rất nhiều mong muốn.

Trong một thế giới lý tưởng ở một thiên hà xa xôi, tất cả các thư viện cố gắng giải quyết vấn đề kiểu tệp này thành đơn giản sẽ thực hiện java.nio.file.spi.FileTypeDetector , bạn sẽ bỏ vào thư viện triển khai thư viện ưa thích gửi vào classpath của bạn và đó sẽ là nó.

Trong thế giới thực, nơi bạn cần phần TL, DR, bạn nên tìm thư viện có hầu hết các ngôi sao bên cạnh tên của nó và sử dụng nó. Đối với trường hợp cụ thể này, tôi không cần một (chưa;)).


3

Tôi đã thử một vài cách để làm điều đó, bao gồm cả những cách đầu tiên được nói bởi @Joshua Fox. Nhưng một số người không nhận ra các mô phỏng thường xuyên như đối với các tệp PDF và một số khác không thể tin được với các tệp giả mạo (Tôi đã thử với tệp RAR có phần mở rộng được đổi thành TIF). Giải pháp tôi tìm thấy, cũng như @Joshua Fox nói một cách hời hợt, là sử dụng MimeUtil2 , như thế này:

MimeUtil2 mimeUtil = new MimeUtil2();
mimeUtil.registerMimeDetector("eu.medsea.mimeutil.detector.MagicMimeMimeDetector");
String mimeType = MimeUtil2.getMostSpecificMimeType(mimeUtil.getMimeTypes(file)).toString();

5
Tôi đã không có thành công nào với MimeUtil2 - gần như mọi thứ đã trở lại dưới dạng ứng dụng / octet-stream. Tôi đã sử dụng MimeUtil.getMimeTypes () với nhiều thành công hơn sau khi khởi tạo với `MimeUtil.registerMimeDetector (" eu.medsea.mimeutil.detector.MagicMimeMimeDetector "); MimeUtil.registerMimeDetector ("eu.medsea.mimeutil.detector.ExtensionMimeDetector"); MimeUtil.registerMimeDetector ("eu.medsea.mimeutil.detector.OpendesktopMimeDetector"); `
Brian Pipa

2
Cảm ơn các giải pháp làm việc. Tài liệu về mime-produc không rõ lắm về cách khởi tạo lớp tiện ích. Cuối cùng đã có nó và chạy, nhưng thay thế chuỗi tên lớp bằng lớp thực tế. MimeUtil.registerMimeDetector (ExtensionMimeDetector. Class.getName ()); Chuỗi mimeType = MimeUtil.getMostSpecificMimeType (MimeUtil.getMimeTypes (tên tệp)). ToString ();
Rob Juurlink

2

Tốt hơn là sử dụng xác nhận hai lớp để tải lên tập tin.

Trước tiên, bạn có thể kiểm tra mimeType và xác nhận nó.

Thứ hai, bạn nên tìm cách chuyển đổi 4 byte đầu tiên của tệp thành thập lục phân và sau đó so sánh nó với các số ma thuật. Sau đó, nó sẽ là một cách thực sự an toàn để kiểm tra xác nhận tập tin.


2

Đây là cách đơn giản nhất tôi tìm thấy để làm điều này:

byte[] byteArray = ...
InputStream is = new BufferedInputStream(new ByteArrayInputStream(byteArray));
String mimeType = URLConnection.guessContentTypeFromStream(is);

Giải pháp rất tốt!
Sherzod

2

Nếu bạn đang làm việc với Servlet và nếu bối cảnh của servlet có sẵn cho bạn, bạn có thể sử dụng:

getServletContext().getMimeType( fileName );

1
getServletContext
điện tử

1

trong tập tin MultipartFile mùa xuân ;

org.springframework.web.multipart.MultipartFile

file.getContentType();


0

Nếu bạn làm việc trên hệ điều hành linux, có một dòng lệnh file --mimetype:

String mimetype(file){

   //1. run cmd
   Object cmd=Runtime.getRuntime().exec("file --mime-type "+file);

   //2 get output of cmd , then 
    //3. parse mimetype
    if(output){return output.split(":")[1].trim(); }
    return "";
}

Sau đó

mimetype("/home/nyapp.war") //  'application/zip'

mimetype("/var/www/ggg/au.mp3") //  'audio/mp3'

2
Điều này sẽ hoạt động, nhưng IMO là một thực tiễn tồi vì nó liên kết mã của bạn với một HĐH cụ thể và yêu cầu tiện ích bên ngoài phải có mặt tại hệ thống đang chạy nó. Đừng hiểu lầm tôi; đó là một giải pháp hoàn toàn hợp lệ, nhưng phá vỡ tính di động - đó là một trong những lý do chính để sử dụng Java ngay từ đầu ...
ToVine

@ToVine: Đối với hồ sơ, tôi sẽ không đồng ý. Không phải mọi chương trình Java đều được yêu cầu để có thể mang theo. Hãy để bối cảnh và lập trình viên đưa ra quyết định đó. vi.wikipedia.org/wiki/Java_Native_Interface
Zahnon

0

Sau khi thử nhiều thư viện khác, tôi đã giải quyết với mime-produc.

<groupId>eu.medsea.mimeutil</groupId>
      <artifactId>mime-util</artifactId>
      <version>2.1.3</version>
</dependency>

File file = new File("D:/test.tif");
MimeUtil.registerMimeDetector("eu.medsea.mimeutil.detector.MagicMimeMimeDetector");
Collection<?> mimeTypes = MimeUtil.getMimeTypes(file);
System.out.println(mimeTypes);

0
public String getFileContentType(String fileName) {
    String fileType = "Undetermined";
    final File file = new File(fileName);
    try
    {
        fileType = Files.probeContentType(file.toPath());
    }
    catch (IOException ioException)
    {
        System.out.println(
                "ERROR: Unable to determine file type for " + fileName
                        + " due to exception " + ioException);
    }
    return fileType;
}

Phương thức này Files.probeContentType (String) có sẵn từ JDK phiên bản 1.7 và nó hoạt động rất tốt đối với tôi.
Reza Rahimi

Cảm ơn, chỉ tôi không thể hiểu tại sao một số người dùng đã bỏ phiếu)))
Vazgen Torosyan

Hoàn toàn không, có lẽ họ có phiên bản cũ hơn của JDK :)))
Reza Rahimi

0

Bạn có thể làm điều đó chỉ với một dòng: MimetypesFileTypeMap (). GetContentType (Tệp mới ("filename.ext")) . Nhìn mã kiểm tra hoàn chỉnh (Java 7):

import java.io.File;
import javax.activation.MimetypesFileTypeMap;
public class MimeTest {
    public static void main(String a[]){
         System.out.println(new MimetypesFileTypeMap().getContentType(
           new File("/path/filename.txt")));
    }
}

Mã này tạo ra đầu ra sau: text / plain


0
File file = new File(PropertiesReader.FILE_PATH);
MimetypesFileTypeMap fileTypeMap = new MimetypesFileTypeMap();
String mimeType = fileTypeMap.getContentType(file);
URLConnection uconnection = file.toURL().openConnection();
mimeType = uconnection.getContentType();

4
Mặc dù mã này có thể giải quyết câu hỏi, bao gồm một lời giải thích thực sự giúp cải thiện chất lượng bài đăng của bạn.
Shree

0

Tôi đã làm điều đó với mã sau đây.

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

public class MimeFileType {

    public static void main(String args[]){

        try{
            URL url = new URL ("https://www.url.com.pdf");

            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            connection.setDoOutput(true);
            InputStream content = (InputStream)connection.getInputStream();
            connection.getHeaderField("Content-Type");

            System.out.println("Content-Type "+ connection.getHeaderField("Content-Type"));

            BufferedReader in = new BufferedReader (new InputStreamReader(content));

        }catch (Exception e){

        }
    }
}

0

Tika Apache.

<!-- https://mvnrepository.com/artifact/org.apache.tika/tika-parsers -->
<dependency>
    <groupId>org.apache.tika</groupId>
    <artifactId>tika-parsers</artifactId>
    <version>1.24</version>
</dependency>

và Hai dòng mã.

Tika tika=new Tika();
tika.detect(inputStream);

Ảnh chụp màn hình bên dưới

nhập mô tả hình ảnh ở đây

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.