use std::cmp::Ordering;
use std::fmt::Debug;
#[allow(unused_imports)]
use std::fs::File;
#[allow(unused_imports)]
use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write};
use std::ops::Range;
use std::str::FromStr;

pub fn start<R: Read, W: Write>(input: R, output: &mut W) {
    let mut scanner = Scanner::new(input.bytes().map(Result::unwrap));
    let n = scanner.next_value::<usize>();
    let m = scanner.next_value::<usize>();

    const MOD_BASE: u64 = 1_000_000_007;

    fn comb(n: u64, mut k: u64) -> u64 {
        if k > (n >> 1) {
            k = n - k;
        }
        let mut nume = 1;
        let mut denom = 1;
        for i in 0..k {
            nume = nume * (n - i) % MOD_BASE;
            denom = denom * (i + 1) % MOD_BASE;
        }
        nume * mod_inv(denom, MOD_BASE) % MOD_BASE
    }

    if m == 1 || n == 1 {
        writeln!(output, "1").unwrap();
        return;
    }

    let mut pf = prime_factorization(m).collect::<Vec<_>>();
    pf.sort();
    let mut c = 1;
    let mut prev = pf[0];
    let mut result = 1;
    for &p in pf.iter().skip(1) {
        if prev != p {
            result = result * comb((c + n - 1) as u64, c as u64) % MOD_BASE;
            prev = p;
            c = 1;
        } else {
            c += 1;
        }
    }
    result = result * comb((c + n - 1) as u64, c as u64) % MOD_BASE;

    writeln!(output, "{}", result).unwrap();
}

////////////////////////////
// Snipetts
////////////////////////////

#[allow(dead_code)]
fn main() {
    let stdin = stdin();
    let stdout = stdout();
    let mut out = BufWriter::new(stdout.lock());
    let input = BufReader::new(stdin.lock());
    start(input, &mut out);
}

// // ファイルの場合
// #[allow(dead_code)]
// fn main() {
//     let mut out = BufWriter::new(File::create("output.txt").unwrap());
//     let input = BufReader::new(File::open("input.txt").unwrap());
//     start(input, &mut out);
// }

// 大きなスタックが必要な場合はこちらを利用する
// #[allow(dead_code)]
// fn main() {
//     std::thread::Builder::new()
//         .name("big stack size".into())
//         .stack_size(32 * 1024 * 1024) // 32 MBのスタックサイズ
//         .spawn(|| {
//             let stdin = stdin();
//             let stdout = stdout();
//             let out = BufWriter::new(stdout.lock());
//             let input = BufReader::new(stdin.lock());
//             start(input, out);
//         })
//         .unwrap()
//         .join()
//         .unwrap();
// }

pub struct Scanner<I: Iterator<Item = u8>> {
    input: I,
}

impl<I: Iterator<Item = u8>> Scanner<I> {
    pub fn new(input: I) -> Self {
        Scanner::<I> { input: input }
    }

    // 文字コードはISO/IEC 8859-1 (Latin-1)とする
    pub fn next_value<T: FromStr>(&mut self) -> T
    where
        <T as FromStr>::Err: Debug,
    {
        T::from_str(
            &(self
                .input
                .by_ref()
                .map(char::from)
                .skip_while(|x| x.is_whitespace())
                .take_while(|x| !x.is_whitespace())
                .collect::<String>()),
        )
        .unwrap()
    }

    // 文字コードはISO/IEC 8859-1 (Latin-1)とする
    #[allow(dead_code)]
    pub fn next_bytes(&mut self) -> Vec<u8> {
        self.input
            .by_ref()
            .skip_while(|&x| char::from(x).is_whitespace())
            .take_while(|&x| !char::from(x).is_whitespace())
            .collect::<Vec<_>>()
    }

    #[allow(dead_code)]
    pub fn next_array<T: FromStr>(&mut self, len: usize) -> Vec<T>
    where
        <T as FromStr>::Err: Debug,
    {
        (0..len).map(|_| self.next_value()).collect()
    }

    #[allow(dead_code)]
    pub fn next_2darray<T: FromStr>(&mut self, x: usize, y: usize) -> Vec<Vec<T>>
    where
        <T as FromStr>::Err: Debug,
    {
        (0..y).map(|_| self.next_array(x)).collect()
    }

    #[allow(dead_code)]
    pub fn next_2d_bytes(&mut self, len: usize) -> Vec<Vec<u8>> {
        (0..len).map(|_| self.next_bytes()).collect()
    }
}

// 1.15.1でReverseを使う
// https://qiita.com/hatoo@github/items/fa14ad36a1b568d14f3e
#[derive(Eq, PartialEq, Clone, Debug)]
pub struct Reverse<T>(pub T);

impl<T: PartialOrd> PartialOrd for Reverse<T> {
    fn partial_cmp(&self, other: &Reverse<T>) -> Option<Ordering> {
        other.0.partial_cmp(&self.0)
    }
}

impl<T: Ord> Ord for Reverse<T> {
    fn cmp(&self, other: &Reverse<T>) -> Ordering {
        other.0.cmp(&self.0)
    }
}

// PartialOrdをTotalOrdにするラッパ
// https://qiita.com/hatoo@github/items/fa14ad36a1b568d14f3e

#[derive(PartialEq, PartialOrd)]
pub struct Total<T>(pub T);

impl<T: PartialEq> Eq for Total<T> {}

impl<T: PartialOrd> Ord for Total<T> {
    fn cmp(&self, other: &Total<T>) -> Ordering {
        self.0.partial_cmp(&other.0).unwrap()
    }
}

// 順列生成
#[allow(dead_code)]
pub fn permutations<'a, T: 'a + From<u8> + Clone + PartialEq + Sized>(
    n: T,
    k: usize,
) -> Box<Iterator<Item = Vec<T>> + 'a>
where
    Range<T>: Iterator<Item = T>,
{
    fn inner<'a, T: 'a + From<u8> + Clone + PartialEq + Sized>(
        array: Vec<T>,
        depth: usize,
        max: T,
    ) -> Box<Iterator<Item = Vec<T>> + 'a>
    where
        Range<T>: Iterator<Item = T>,
    {
        let na = (T::from(0)..max.clone()).flat_map(move |x| {
            if array.contains(&x) {
                None
            } else {
                let mut newarray = array.clone();
                newarray.push(x);
                Some(newarray)
            }
        });
        if depth > 0 {
            Box::new(na.flat_map(move |x| inner(x, depth - 1, max.clone())))
        } else {
            Box::new(na)
        }
    }
    inner(Vec::new(), k - 1, n)
}

// 組み合わせ生成
#[allow(dead_code)]
pub fn combinations(n: usize, r: usize) -> Box<Iterator<Item = Vec<u8>>> {
    fn inner(
        array: Vec<u8>,
        depth: usize,
        min: usize,
        max: usize,
    ) -> Box<Iterator<Item = Vec<u8>>> {
        let na = (min..(max - depth)).map(move |x| {
            let mut newarray = array.clone();
            newarray.push(x as u8);
            newarray
        });
        if depth > 0 {
            Box::new(na.flat_map(move |x| {
                let next = usize::from(*x.last().unwrap()) + 1;
                inner(x, depth - 1, next, max)
            }))
        } else {
            Box::new(na)
        }
    }
    inner(Vec::new(), r - 1, 0, n)
}

#[derive(Copy)]
struct AhoCorasickState {
    next: [usize; 256],
}

impl Clone for AhoCorasickState {
    fn clone(&self) -> AhoCorasickState {
        AhoCorasickState { next: self.next }
    }
}

impl AhoCorasickState {
    fn new() -> AhoCorasickState {
        AhoCorasickState { next: [0; 256] }
    }

    fn set_next(&mut self, index: usize, value: usize) {
        self.next[index] = value;
    }

    fn get_next(&self, index: usize) -> Option<usize> {
        if self.next[index] != 0 {
            Some(self.next[index])
        } else {
            None
        }
    }

    fn keys(&self) -> Vec<usize> {
        self.next
            .iter()
            .enumerate()
            .filter(|&(_, &y)| y != 0)
            .map(|(y, _)| y)
            .collect::<Vec<_>>()
    }
}

#[allow(dead_code)]
pub struct AhoCorasickAutomaton {
    states: Vec<AhoCorasickState>,
    failure_link: Vec<usize>,
    output: Vec<Vec<Vec<u8>>>,
}

impl AhoCorasickAutomaton {
    pub fn new(terms: &[Vec<u8>]) -> AhoCorasickAutomaton {
        let mut result = AhoCorasickAutomaton {
            states: vec![AhoCorasickState::new()],
            failure_link: Vec::new(),
            output: vec![Vec::new()],
        };
        result.make_trie(terms);
        result.make_failure();
        result
    }

    fn make_trie(&mut self, terms: &[Vec<u8>]) {
        for term in terms {
            let mut current = 0;
            for &c in term {
                let next_key = usize::from(c);
                current = if let Some(next) = self.states[current].get_next(next_key) {
                    next
                } else {
                    let next = self.states.len();
                    self.states.push(AhoCorasickState::new());
                    self.states[current].set_next(next_key, next);
                    self.output.push(Vec::new());
                    next
                };
            }
            self.output[current].push(term.clone());
        }
    }

    fn make_failure(&mut self) {
        let mut failure = vec![0; self.states.len()];
        let mut queue = vec![0];
        while let Some(s) = queue.pop() {
            for x in self.states[s].keys() {
                if let Some(next) = self.g(s, x) {
                    queue.push(next);
                    if s != 0 {
                        let mut f = failure[s];
                        while let None = self.g(f, x) {
                            f = failure[f];
                        }
                        failure[next] = self.g(f, x).unwrap();
                        let mut n = self.output[failure[next]].clone();
                        self.output[next].append(&mut n);
                    }
                }
            }
        }
        self.failure_link = failure;
    }

    fn g(&self, s: usize, x: usize) -> Option<usize> {
        if let Some(r) = self.states[s].get_next(x) {
            Some(r)
        } else if s == 0 {
            Some(0)
        } else {
            None
        }
    }

    pub fn match_iter<'a>(&'a self, query: &'a [u8]) -> AhoCorasickAutomatonMatcher<'a> {
        AhoCorasickAutomatonMatcher {
            index: 0,
            state: 0,
            sindex: 0,
            ac: self,
            query: query,
        }
    }
}

#[derive(Debug, PartialEq, Eq)]
pub struct MatchValue<'a> {
    pub start: usize,
    pub end: usize,
    pub value: &'a [u8],
}

pub struct AhoCorasickAutomatonMatcher<'a> {
    index: usize,
    state: usize,
    sindex: usize,
    ac: &'a AhoCorasickAutomaton,
    query: &'a [u8],
}

impl<'a> Iterator for AhoCorasickAutomatonMatcher<'a> {
    type Item = MatchValue<'a>;

    fn next(&mut self) -> Option<MatchValue<'a>> {
        loop {
            if let Some(r) = self.ac.output[self.state].get(self.sindex) {
                self.sindex += 1;
                return Some(MatchValue {
                    start: self.index - r.len(),
                    end: self.index - 1,
                    value: &r,
                });
            }

            self.sindex = 0;
            if let Some(&c) = self.query.get(self.index) {
                self.index += 1;
                let c = usize::from(c);
                while let None = self.ac.g(self.state, c) {
                    self.state = self.ac.failure_link[self.state];
                }
                self.state = self.ac.g(self.state, c).unwrap();
            } else {
                return None;
            }
        }
    }
}

// Sunday's
pub struct QuickSearchTable {
    table: [usize; 256],
    output: Vec<u8>,
}

impl QuickSearchTable {
    pub fn new(term: &[u8]) -> QuickSearchTable {
        let mut r = [term.len() + 1; 256];
        for (i, &c) in term.iter().enumerate() {
            r[usize::from(c)] = term.len() - i;
        }
        QuickSearchTable {
            table: r,
            output: term.to_vec(),
        }
    }

    pub fn match_iter<'a>(&'a self, query: &'a [u8]) -> QuickSearchMatcher<'a> {
        QuickSearchMatcher {
            index: 0,
            qst: self,
            query: query,
        }
    }
}

pub struct QuickSearchMatcher<'a> {
    index: usize,
    qst: &'a QuickSearchTable,
    query: &'a [u8],
}

impl<'a> Iterator for QuickSearchMatcher<'a> {
    type Item = MatchValue<'a>;

    fn next(&mut self) -> Option<MatchValue<'a>> {
        while self.index + self.qst.output.len() <= self.query.len() {
            let mut j = 0;
            while j < self.qst.output.len() {
                if self.qst.output[j] != self.query[self.index + j] {
                    break;
                }
                j += 1;
            }
            let before_index = self.index;
            self.index += self
                .query
                .get(self.index + self.qst.output.len())
                .and_then(|&x| self.qst.table.get(usize::from(x)))
                .map_or_else(|| self.qst.output.len() + 1, |&x| x);

            if j == self.qst.output.len() {
                return Some(MatchValue {
                    start: before_index,
                    end: before_index + self.qst.output.len() - 1,
                    value: &self.qst.output,
                });
            }
        }
        None
    }
}

#[allow(dead_code)]
pub struct ChainedOrdering(Ordering);

impl ChainedOrdering {
    pub fn then(self, other: Ordering) -> Ordering {
        match self.0 {
            Ordering::Equal => other,
            o => o,
        }
    }

    pub fn and_then<F: FnOnce() -> Ordering>(self, other: F) -> Ordering {
        match self.0 {
            Ordering::Equal => other(),
            o => o,
        }
    }
}

pub fn lis_by<T, F: FnMut(&T, &T) -> Ordering>(input: &[T], mut f: F) -> usize {
    let mut result = Vec::new();
    for c in input {
        if let Err(s) = result.binary_search_by(|&x| f(x, c)) {
            if s == result.len() {
                result.push(c);
            } else {
                result[s] = c;
            }
        }
    }
    result.len()
}

pub fn lis_by_key<T, K: Ord, F: FnMut(&T) -> K>(input: &[T], mut f: F) -> usize {
    lis_by(input, |a, b| f(a).cmp(&f(b)))
}

pub fn lis<T: Ord>(input: &[T]) -> usize {
    lis_by(input, |a, b| a.cmp(b))
}

pub fn mod_pow(mut base: usize, mut pow: usize, modu: usize) -> usize {
    let mut result = 1;
    while pow > 0 {
        if (pow & 1) == 1 {
            result = (result * base) % modu;
        }
        pow >>= 1;
        base = (base * base) % modu;
    }
    result
}

const MIRROR_RABIN_BASE_32: &'static [usize] = &[2, 7, 61];
#[cfg(target_pointer_width = "64")]
const MIRROR_RABIN_BASE_64: &'static [usize] =
    &[2, 325, 9_375, 28_178, 450_775, 9_780_504, 1_795_265_022];

#[cfg(target_pointer_width = "32")]
fn miller_rabin_base(n: usize) -> &'static [usize] {
    MIRROR_RABIN_BASE_32
}

#[cfg(target_pointer_width = "64")]
fn miller_rabin_base(n: usize) -> &'static [usize] {
    match n {
        0...4_759_123_142 => MIRROR_RABIN_BASE_32,
        _ => MIRROR_RABIN_BASE_64,
    }
}

// Miller–Rabin primality test
pub fn is_prime(n: usize) -> bool {
    if n == 2 {
        return true;
    }

    if n == 1 || (n & 1) == 0 {
        return false;
    }

    let mut d = n - 1;
    let s = d.trailing_zeros();
    d >>= s;

    'outer: for &b in miller_rabin_base(n) {
        if b == n {
            continue;
        }
        let mut c = mod_pow(b, d, n);
        if c == 1 {
            continue;
        }
        for _ in 0..s {
            if c == n - 1 {
                continue 'outer;
            }
            c = (c * c) % n;
        }
        return false;
    }
    true
}

pub fn gcd(mut m: usize, mut n: usize) -> usize {
    if m == 0 || n == 0 {
        return m | n;
    }
    let shift = (m | n).trailing_zeros();

    n >>= n.trailing_zeros();

    while m != 0 {
        m >>= m.trailing_zeros();
        if n > m {
            ::std::mem::swap(&mut n, &mut m)
        }
        m -= n;
    }
    n << shift
}

pub fn prime_factorization(n: usize) -> PrimeFactorIterator {
    PrimeFactorIterator(n)
}

pub struct PrimeFactorIterator(usize);

impl Iterator for PrimeFactorIterator {
    type Item = usize;

    fn next(&mut self) -> Option<usize> {
        if self.0 == 0 {
            return None;
        }

        let result = if self.0 == 1 {
            1
        } else {
            factorization(self.0)
        };

        self.0 /= result;
        if self.0 == 1 {
            self.0 = 0;
        }
        Some(result)
    }
}

// Pollard's rho algorithm
fn factorization(n: usize) -> usize {
    if (n & 1) == 0 {
        return 2;
    }
    if is_prime(n) {
        return n;
    }

    fn f(x: usize, n: usize, i: usize) -> usize {
        ((((i + 1) * 4) + 1) * x) % n
    }

    let mut i = 0;
    loop {
        let mut x = 2;
        let mut y = 2;
        let mut d = 1;

        while d == 1 {
            x = f(x, n, i);
            y = f(f(y, n, i), n, i);
            d = gcd(if x > y { x - y } else { y - x }, n);
        }
        // 本当はメモ化しておくのがよいと思う
        if d != n {
            return factorization(d);
        }
        i += 1;
    }
}

fn mod_inv(n: u64, base: u64) -> u64 {
    let mut now = (0, 1, base);
    let mut prev = (1, 0, n % base);

    while now.2 > 0 {
        let q = (prev.2 / now.2) as u64;

        let next = (
            (prev.0 + (base - now.0) * q) % base,
            (prev.1 + (base - now.1) * q) % base,
            prev.2 - q * now.2,
        );
        prev = ::std::mem::replace(&mut now, next);
    }
    prev.0
}


Measured memory_bytes: 4352