#[allow(unused_macros)]
macro_rules ! read_value {($ next : expr , ($ ($ t : tt ) ,* ) ) => {($ (read_value ! ($ next , $ t ) ) ,* ) } ; ($ next : expr , [$ t : tt ; $ len : expr ] ) => {(0 ..$ len ) . map (| _ | read_value ! ($ next , $ t ) ) . collect ::< Vec < _ >> () } ; ($ next : expr , chars ) => {read_value ! ($ next , String ) . chars () . collect ::< Vec < char >> () } ; ($ next : expr , usize1 ) => {read_value ! ($ next , usize ) - 1 } ; ($ next : expr , $ t : ty ) => {$ next () . parse ::<$ t > () . expect ("Parse error" ) } ; }
#[allow(unused_macros)]
macro_rules ! input_inner {($ next : expr ) => {} ; ($ next : expr , ) => {} ; ($ next : expr , $ var : ident : $ t : tt $ ($ r : tt ) * ) => {let $ var = read_value ! ($ next , $ t ) ; input_inner ! {$ next $ ($ r ) * } } ; }
#[allow(unused_macros)]
macro_rules ! input {(source = $ s : expr , $ ($ r : tt ) * ) => {let mut iter = $ s . split_whitespace () ; let mut next = || {iter . next () . unwrap () } ; input_inner ! {next , $ ($ r ) * } } ; ($ ($ r : tt ) * ) => {let stdin = std :: io :: stdin () ; let mut bytes = std :: io :: Read :: bytes (std :: io :: BufReader :: new (stdin . lock () ) ) ; let mut next = move || -> String {bytes . by_ref () . map (| r | r . unwrap () as char ) . skip_while (| c | c . is_whitespace () ) . take_while (| c |! c . is_whitespace () ) . collect () } ; input_inner ! {next , $ ($ r ) * } } ; }

#[allow(dead_code)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
struct ModInt {
    value: i64,
    modulo: i64,
}
#[allow(dead_code)]
fn ext_gcd(a: i64, b: i64) -> (i64, i64, i64) {
    if b == 0 {
        (a, 1, 0)
    } else {
        let (gcd, x, y) = ext_gcd(b, a % b);
        (gcd, y, x - (a / b) * y)
    }
}
#[allow(dead_code)]
impl ModInt {
    fn new(mut value: i64, modulo: i64) -> Self {
        if value >= modulo {
            value %= modulo;
        } else if value <= 0 {
            value = (value % modulo + modulo) % modulo;
        }
        ModInt { value, modulo }
    }
    fn inv(self) -> Self {
        let modulo = self.modulo;
        let (_, inv, _) = ext_gcd(self.value, modulo);
        ModInt {
            value: (inv + modulo) % modulo,
            modulo,
        }
    }
    fn pow(self, n: i64) -> Self {
        if n < 0 {
            self.pow(-n).inv()
        } else if n == 0 {
            ModInt {
                value: 1,
                modulo: self.modulo,
            }
        } else if n == 1 {
            self
        } else {
            let mut x = (self * self).pow(n / 2);
            if n % 2 == 1 {
                x *= self;
            }
            x
        }
    }
    fn factor(n: i64, modulo: i64) -> Self {
        let mut fact = ModInt::new(1_i64, modulo);
        for i in 1..=n {
            fact *= i;
        }
        fact
    }
    fn combination(n: i64, r: i64, modulo: i64) -> Self {
        if n < r {
            return ModInt::new(0, modulo);
        }
        let a1 = ModInt::factor(n, modulo);
        let a2 = ModInt::factor(r, modulo);
        let a3 = ModInt::factor(n - r, modulo);
        a1 / a2 / a3
    }
}
#[allow(dead_code)]
impl std::ops::Neg for ModInt {
    type Output = Self;
    fn neg(self) -> Self {
        if self.value == 0 {
            self
        } else {
            ModInt {
                value: self.modulo - self.value,
                modulo: self.modulo,
            }
        }
    }
}
#[allow(dead_code)]
impl std::ops::Add<i64> for ModInt {
    type Output = Self;
    fn add(self, rhs: i64) -> Self {
        ModInt::new(self.value + rhs, self.modulo)
    }
}
#[allow(dead_code)]
impl std::ops::Add<ModInt> for i64 {
    type Output = ModInt;
    fn add(self, rhs: ModInt) -> ModInt {
        rhs + self
    }
}
#[allow(dead_code)]
impl std::ops::Add for ModInt {
    type Output = Self;
    fn add(self, rhs: ModInt) -> Self {
        self + rhs.value
    }
}
#[allow(dead_code)]
impl std::ops::AddAssign<i64> for ModInt {
    fn add_assign(&mut self, rhs: i64) {
        self.value = ModInt::new(self.value + rhs, self.modulo).value;
    }
}
#[allow(dead_code)]
impl std::ops::AddAssign for ModInt {
    fn add_assign(&mut self, rhs: ModInt) {
        *self += rhs.value;
    }
}
#[allow(dead_code)]
impl std::ops::Sub<i64> for ModInt {
    type Output = Self;
    fn sub(self, rhs: i64) -> Self {
        ModInt::new(self.value - rhs, self.modulo)
    }
}
#[allow(dead_code)]
impl std::ops::Sub for ModInt {
    type Output = Self;
    fn sub(self, rhs: ModInt) -> Self {
        self - rhs.value
    }
}
#[allow(dead_code)]
impl std::ops::Sub<ModInt> for i64 {
    type Output = ModInt;
    fn sub(self, rhs: ModInt) -> ModInt {
        ModInt::new(self - rhs.value, rhs.modulo)
    }
}
#[allow(dead_code)]
impl std::ops::SubAssign<i64> for ModInt {
    fn sub_assign(&mut self, rhs: i64) {
        self.value = ModInt::new(self.value - rhs, self.modulo).value;
    }
}
#[allow(dead_code)]
impl std::ops::SubAssign for ModInt {
    fn sub_assign(&mut self, rhs: ModInt) {
        *self -= rhs.value;
    }
}
#[allow(dead_code)]
impl std::ops::Mul<i64> for ModInt {
    type Output = Self;
    fn mul(self, rhs: i64) -> Self {
        ModInt::new(self.value * rhs, self.modulo)
    }
}
#[allow(dead_code)]
impl std::ops::Mul for ModInt {
    type Output = Self;
    fn mul(self, rhs: ModInt) -> Self {
        self * rhs.value
    }
}
#[allow(dead_code)]
impl std::ops::Mul<ModInt> for i64 {
    type Output = ModInt;
    fn mul(self, rhs: ModInt) -> ModInt {
        rhs * self
    }
}
#[allow(dead_code)]
impl std::ops::MulAssign<i64> for ModInt {
    fn mul_assign(&mut self, rhs: i64) {
        self.value = ModInt::new(self.value * rhs, self.modulo).value;
    }
}
#[allow(dead_code)]
impl std::ops::MulAssign for ModInt {
    fn mul_assign(&mut self, rhs: ModInt) {
        *self *= rhs.value;
    }
}
#[allow(clippy::suspicious_arithmetic_impl)]
#[allow(dead_code)]
impl std::ops::Div for ModInt {
    type Output = Self;
    fn div(self, rhs: ModInt) -> Self {
        self * rhs.inv()
    }
}
#[allow(dead_code)]
impl std::ops::Div<i64> for ModInt {
    type Output = Self;
    fn div(self, rhs: i64) -> Self {
        self / ModInt::new(rhs, self.modulo)
    }
}
#[allow(clippy::suspicious_arithmetic_impl)]
#[allow(dead_code)]
impl std::ops::Div<ModInt> for i64 {
    type Output = ModInt;
    fn div(self, rhs: ModInt) -> ModInt {
        rhs.inv() * self
    }
}
#[allow(dead_code)]
impl std::ops::DivAssign for ModInt {
    fn div_assign(&mut self, rhs: ModInt) {
        self.value = (*self / rhs).value;
    }
}
#[allow(dead_code)]
impl std::ops::DivAssign<i64> for ModInt {
    fn div_assign(&mut self, rhs: i64) {
        *self /= ModInt::new(rhs, self.modulo);
    }
}

/// assume n >= 2
fn prime_factorization(n: i64) -> Vec<(i64, i64)> {
    let mut x = n;
    let mut factorization = vec![];

    while x > 1 {
        let mut is_prime = true;
        for i in 2..=x {
            if i * i > x {
                break;
            }
            if x % i == 0 {
                let mut power = 0;
                while x % i == 0 {
                    x /= i;
                    power += 1;
                }
                factorization.push((i, power));
                is_prime = false;
                break;
            }
        }
        if is_prime {
            factorization.push((x, 1));
            break;
        }
    }
    factorization
}


fn main() {
    input! {
        n: i64,
        m: i64,
    }
    let modulo = 1_000_000_007;

    let mut ret = ModInt::new(1, modulo);
    let fct = prime_factorization(m);
    for (_, c) in prime_factorization(m) {
        ret *= ModInt::combination(c + n - 1, n - 1, modulo);
    }
    println!("{}", ret.value);
}


Measured memory_bytes: 2148